diff --git a/.eslintrc b/.eslintrc index 4ea7f0edff..84cdc6bb85 100644 --- a/.eslintrc +++ b/.eslintrc @@ -119,7 +119,6 @@ "getCookies": true, "get_url_arg": true, "QUnit": true, - "Snap": true, - "mina": true + "JsBarcode": true } } diff --git a/.travis.yml b/.travis.yml index ef03adb693..38f61ba37b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ install: - sudo apt-get purge -y mysql-common mysql-server mysql-client - nvm install v7.10.0 - wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py + - sudo python install.py --develop --user travis --without-bench-setup - sudo pip install -e ~/bench @@ -42,6 +43,7 @@ before_script: - echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root -ptravis - echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root -ptravis + - cd ~/frappe-bench - bench use test_site - bench reinstall --yes diff --git a/frappe/__init__.py b/frappe/__init__.py index 1ad39e0b32..0d77a099df 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -6,7 +6,7 @@ globals attached to frappe module """ from __future__ import unicode_literals, print_function -from six import iteritems, text_type, string_types +from six import iteritems, binary_type, text_type, string_types from werkzeug.local import Local, release_local import os, sys, importlib, inspect, json @@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json from .exceptions import * from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template -__version__ = '9.1.11' +__version__ = '9.2.0' __title__ = "Frappe Framework" local = Local() @@ -61,7 +61,7 @@ def as_unicode(text, encoding='utf-8'): return text elif text==None: return '' - elif isinstance(text, string_types): + elif isinstance(text, binary_type): return text_type(text, encoding) else: return text_type(text) @@ -475,13 +475,26 @@ def only_for(roles): if not roles.intersection(myroles): raise PermissionError +def get_domain_data(module): + try: + domain_data = get_hooks('domains') + if module in domain_data: + return _dict(get_attr(get_hooks('domains')[module][0] + '.data')) + else: + return _dict() + except ImportError: + if local.flags.in_test: + return _dict() + else: + raise + + def clear_cache(user=None, doctype=None): """Clear **User**, **DocType** or global cache. :param user: If user is given, only user cache is cleared. :param doctype: If doctype is given, only DocType cache is cleared.""" import frappe.sessions - from frappe.core.doctype.domain_settings.domain_settings import clear_domain_cache if doctype: import frappe.model.meta frappe.model.meta.clear_cache(doctype) @@ -493,7 +506,6 @@ def clear_cache(user=None, doctype=None): frappe.sessions.clear_cache() translate.clear_cache() reset_metadata_version() - clear_domain_cache() local.cache = {} local.new_doc_templates = {} @@ -1319,6 +1331,20 @@ def enqueue(*args, **kwargs): import frappe.utils.background_jobs return frappe.utils.background_jobs.enqueue(*args, **kwargs) +def enqueue_doc(*args, **kwargs): + ''' + Enqueue method to be executed using a background worker + + :param doctype: DocType of the document on which you want to run the event + :param name: Name of the document on which you want to run the event + :param method: method string or method object + :param queue: (optional) should be either long, default or short + :param timeout: (optional) should be set according to the functions + :param kwargs: keyword arguments to be passed to the method + ''' + import frappe.utils.background_jobs + return frappe.utils.background_jobs.enqueue_doc(*args, **kwargs) + def get_doctype_app(doctype): def _get_doctype_app(): doctype_module = local.db.get_value("DocType", doctype, "module") @@ -1371,4 +1397,4 @@ def get_system_settings(key): def get_active_domains(): from frappe.core.doctype.domain_settings.domain_settings import get_active_domains - return get_active_domains() \ No newline at end of file + return get_active_domains() diff --git a/frappe/app.py b/frappe/app.py index 79cfdfe442..b2e19beff0 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -128,7 +128,7 @@ def handle_exception(e): http_status_code = getattr(e, "http_status_code", 500) return_as_message = False - if frappe.local.is_ajax or 'application/json' in frappe.local.request.headers.get('Accept', ''): + if frappe.local.is_ajax or 'application/json' in frappe.get_request_header('Accept'): # handle ajax responses first # if the request is ajax, send back the trace or error message response = frappe.utils.response.report_error(http_status_code) diff --git a/frappe/build.js b/frappe/build.js index 9a4f0c2fc9..de12e31ca0 100644 --- a/frappe/build.js +++ b/frappe/build.js @@ -60,11 +60,11 @@ function watch() { io.emit('reload_css', filename); } }); - watch_js(function (filename) { - if(socket_connection) { - io.emit('reload_js', filename); - } - }); + // watch_js(function (filename) { + // if(socket_connection) { + // io.emit('reload_js', filename); + // } + // }); watch_build_json(); }); diff --git a/frappe/config/integrations.py b/frappe/config/integrations.py index cb69cb2a6d..7c28372382 100644 --- a/frappe/config/integrations.py +++ b/frappe/config/integrations.py @@ -32,6 +32,11 @@ def get_data(): "name": "Dropbox Settings", "description": _("Dropbox backup settings"), }, + { + "type": "doctype", + "name": "S3 Backup Settings", + "description": _("S3 Backup Settings"), + }, ] }, { diff --git a/frappe/core/doctype/communication/comment.py b/frappe/core/doctype/communication/comment.py index b5f0f141bc..ca98383f94 100644 --- a/frappe/core/doctype/communication/comment.py +++ b/frappe/core/doctype/communication/comment.py @@ -81,7 +81,17 @@ def notify_mentions(doc): return sender_fullname = get_fullname(frappe.session.user) - parent_doc_label = "{0} {1}".format(_(doc.reference_doctype), doc.reference_name) + title_field = frappe.get_meta(doc.reference_doctype).get_title_field() + title = doc.reference_name if title_field == "name" else \ + frappe.db.get_value(doc.reference_doctype, doc.reference_name, title_field) + + if title != doc.reference_name: + parent_doc_label = "{0}: {1} (#{2})".format(_(doc.reference_doctype), + title, doc.reference_name) + else: + parent_doc_label = "{0}: {1}".format(_(doc.reference_doctype), + doc.reference_name) + subject = _("{0} mentioned you in a comment").format(sender_fullname) recipients = [frappe.db.get_value("User", {"enabled": 1, "username": username, "user_type": "System User"}) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index d259b60cbd..5a63381d91 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -9,7 +9,7 @@ from frappe.utils import validate_email_add, get_fullname, strip_html, cstr from frappe.core.doctype.communication.comment import (notify_mentions, update_comment_in_doc, on_trash) from frappe.core.doctype.communication.email import (validate_email, - notify, _notify, update_parent_status) + notify, _notify, update_parent_mins_to_first_response) from frappe.utils.bot import BotReply from frappe.utils import parse_addr @@ -95,7 +95,7 @@ class Communication(Document): def on_update(self): """Update parent status as `Open` or `Replied`.""" if self.comment_type != 'Updated': - update_parent_status(self) + update_parent_mins_to_first_response(self) update_comment_in_doc(self) self.bot_reply() diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index c17ac1f5c8..28c2e5ff0d 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -164,32 +164,30 @@ def _notify(doc, print_html=None, print_format=None, attachments=None, is_notification=True if doc.sent_or_received =="Received" else False ) -def update_parent_status(doc): - """Update status of parent document based on who is replying.""" +def update_parent_mins_to_first_response(doc): + """Update mins_to_first_communication of parent document based on who is replying.""" parent = doc.get_parent_doc() if not parent: return - # update parent status only if we create the Email communication + # update parent mins_to_first_communication only if we create the Email communication # ignore in case of only Comment is added if doc.communication_type == "Comment": return status_field = parent.meta.get_field("status") - if status_field: options = (status_field.options or '').splitlines() - # if status has a "Replied" option, then update the status - if 'Replied' in options: - to_status = "Open" if doc.sent_or_received=="Received" else "Replied" - - if to_status in options: - parent.db_set("status", to_status) + # if status has a "Replied" option, then update the status for received communication + if ('Replied' in options) and doc.sent_or_received=="Received": + parent.db_set("status", "Open") + else: + # update the modified date for document + parent.update_modified() update_mins_to_first_communication(parent, doc) parent.run_method('notify_communication', doc) - parent.notify_update() def get_recipients_and_cc(doc, recipients, cc, fetched_from_email_account=False): diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index dac2ff6423..b00b0c7b07 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -96,7 +96,7 @@ "no_copy": 0, "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime\nSignature", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime\nSignature", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -1364,7 +1364,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-08-29 15:30:55.489568", + "modified": "2017-10-07 19:20:15.888708", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index c5b9dc436a..be045fbb02 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -321,7 +321,7 @@ class DocType(Document): def export_doc(self): """Export to standard folder `[module]/doctype/[name]/[name].json`.""" from frappe.modules.export_file import export_to_files - export_to_files(record_list=[['DocType', self.name]]) + export_to_files(record_list=[['DocType', self.name]], create_init=True) def import_doc(self): """Import from standard folder `[module]/doctype/[name]/[name].json`.""" diff --git a/frappe/core/doctype/domain/domain.py b/frappe/core/doctype/domain/domain.py index d8a571537c..c6b1766235 100644 --- a/frappe/core/doctype/domain/domain.py +++ b/frappe/core/doctype/domain/domain.py @@ -4,7 +4,89 @@ from __future__ import unicode_literals import frappe + from frappe.model.document import Document +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields class Domain(Document): - pass + '''Domain documents are created automatically when DocTypes + with "Restricted" domains are imported during + installation or migration''' + def setup_domain(self): + '''Setup domain icons, permissions, custom fields etc.''' + self.setup_data() + self.setup_roles() + self.setup_properties() + self.set_values() + if not int(frappe.db.get_single_value('System Settings', 'setup_complete') or 0): + # if setup not complete, setup desktop etc. + self.setup_sidebar_items() + self.setup_desktop_icons() + self.set_default_portal_role() + + if self.data.custom_fields: + create_custom_fields(self.data.custom_fields) + + if self.data.on_setup: + # custom on_setup method + frappe.get_attr(self.data.on_setup)() + + + def setup_roles(self): + '''Enable roles that are restricted to this domain''' + if self.data.restricted_roles: + for role_name in self.data.restricted_roles: + role = frappe.get_doc('Role', role_name) + role.disabled = 0 + role.save() + + def setup_data(self, domain=None): + '''Load domain info via hooks''' + self.data = frappe.get_domain_data(self.name) + + def get_domain_data(self, module): + return frappe.get_attr(frappe.get_hooks('domains')[self.name] + '.data') + + def set_default_portal_role(self): + '''Set default portal role based on domain''' + if self.data.get('default_portal_role'): + frappe.db.set_value('Portal Settings', None, 'default_role', + self.data.get('default_portal_role')) + + def setup_desktop_icons(self): + '''set desktop icons form `data.desktop_icons`''' + from frappe.desk.doctype.desktop_icon.desktop_icon import set_desktop_icons + if self.data.desktop_icons: + set_desktop_icons(self.data.desktop_icons) + + def setup_properties(self): + if self.data.properties: + for args in self.data.properties: + frappe.make_property_setter(args) + + + def set_values(self): + '''set values based on `data.set_value`''' + if self.data.set_value: + for args in self.data.set_value: + doc = frappe.get_doc(args[0], args[1] or args[0]) + doc.set(args[2], args[3]) + doc.save() + + def setup_sidebar_items(self): + '''Enable / disable sidebar items''' + if self.data.allow_sidebar_items: + # disable all + frappe.db.sql('update `tabPortal Menu Item` set enabled=0') + + # enable + frappe.db.sql('''update `tabPortal Menu Item` set enabled=1 + where route in ({0})'''.format(', '.join(['"{0}"'.format(d) for d in self.data.allow_sidebar_items]))) + + if self.data.remove_sidebar_items: + # disable all + frappe.db.sql('update `tabPortal Menu Item` set enabled=1') + + # enable + frappe.db.sql('''update `tabPortal Menu Item` set enabled=0 + where route in ({0})'''.format(', '.join(['"{0}"'.format(d) for d in self.data.remove_sidebar_items]))) diff --git a/frappe/core/doctype/domain_settings/domain_settings.py b/frappe/core/doctype/domain_settings/domain_settings.py index cfe5010835..b3e1b133ae 100644 --- a/frappe/core/doctype/domain_settings/domain_settings.py +++ b/frappe/core/doctype/domain_settings/domain_settings.py @@ -7,8 +7,46 @@ import frappe from frappe.model.document import Document class DomainSettings(Document): + def set_active_domains(self, domains): + self.active_domains = [] + for d in domains: + self.append('active_domains', dict(domain=d)) + self.save() + def on_update(self): - clear_domain_cache() + for d in self.active_domains: + domain = frappe.get_doc('Domain', d.domain) + domain.setup_domain() + + self.restrict_roles_and_modules() + frappe.clear_cache() + + def restrict_roles_and_modules(self): + '''Disable all restricted roles and set `restrict_to_domain` property in Module Def''' + active_domains = frappe.get_active_domains() + all_domains = (frappe.get_hooks('domains') or {}).keys() + + def remove_role(role): + frappe.db.sql('delete from `tabHas Role` where role=%s', role) + frappe.set_value('Role', role, 'disabled', 1) + + for domain in all_domains: + data = frappe.get_domain_data(domain) + if not frappe.db.get_value('Domain', domain): + frappe.get_doc(dict(doctype='Domain', domain=domain)).insert() + if 'modules' in data: + for module in data.get('modules'): + frappe.db.set_value('Module Def', module, 'restrict_to_domain', domain) + + if 'restricted_roles' in data: + for role in data['restricted_roles']: + if not frappe.db.get_value('Role', role): + frappe.get_doc(dict(doctype='Role', role_name=role)).insert() + frappe.db.set_value('Role', role, 'restrict_to_domain', domain) + + if domain not in active_domains: + remove_role(role) + def get_active_domains(): """ get the domains set in the Domain Settings as active domain """ @@ -33,6 +71,3 @@ def get_active_modules(): return active_modules return frappe.cache().get_value('active_modules', _get_active_modules) - -def clear_domain_cache(): - frappe.cache().delete_key(['active_domains', 'active_modules']) diff --git a/frappe/core/doctype/feedback_trigger/test_feedback_trigger.py b/frappe/core/doctype/feedback_trigger/test_feedback_trigger.py index 1b07c432d5..c9c4fca527 100644 --- a/frappe/core/doctype/feedback_trigger/test_feedback_trigger.py +++ b/frappe/core/doctype/feedback_trigger/test_feedback_trigger.py @@ -72,6 +72,7 @@ class TestFeedbackTrigger(unittest.TestCase): }).insert(ignore_permissions=True) # check if feedback mail alert is triggered + todo.reload() todo.status = "Closed" todo.save(ignore_permissions=True) @@ -112,6 +113,7 @@ class TestFeedbackTrigger(unittest.TestCase): reference_doctype="ToDo", reference_name=todo.name, feedback="Thank You !!", rating=4, fullname="Test User") # auto feedback request should trigger only once + todo.reload() todo.save(ignore_permissions=True) email_queue = frappe.db.sql("""select name from `tabEmail Queue` where reference_doctype='ToDo' and reference_name='{0}'""".format(todo.name)) @@ -125,11 +127,10 @@ class TestFeedbackTrigger(unittest.TestCase): "communication_type": "Feedback" }) self.assertFalse(communications) - + feedback_requests = frappe.get_all("Feedback Request", { "reference_doctype": "ToDo", "reference_name": todo.name, "is_feedback_submitted": 0 }) self.assertFalse(feedback_requests) - \ No newline at end of file diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index fd53fad44b..25cd8e5e67 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -84,7 +84,7 @@ class Report(Document): if self.is_standard == 'Yes' and (frappe.local.conf.get('developer_mode') or 0) == 1: export_to_files(record_list=[['Report', self.name]], - record_module=self.module) + record_module=self.module, create_init=True) self.create_report_py() diff --git a/frappe/core/doctype/sms_parameter/sms_parameter.json b/frappe/core/doctype/sms_parameter/sms_parameter.json index b5648ade80..43b93ed182 100755 --- a/frappe/core/doctype/sms_parameter/sms_parameter.json +++ b/frappe/core/doctype/sms_parameter/sms_parameter.json @@ -71,6 +71,36 @@ "set_only_once": 0, "unique": 0, "width": "150px" + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "header", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Header", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "has_web_view": 0, @@ -83,8 +113,8 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-07-22 22:52:53.309396", - "modified_by": "chude.osiegbu@manqala.com", + "modified": "2017-10-13 16:48:00.518463", + "modified_by": "Administrator", "module": "Core", "name": "SMS Parameter", "owner": "Administrator", diff --git a/frappe/core/doctype/sms_settings/sms_settings.json b/frappe/core/doctype/sms_settings/sms_settings.json index ac911f2ecb..948329d081 100755 --- a/frappe/core/doctype/sms_settings/sms_settings.json +++ b/frappe/core/doctype/sms_settings/sms_settings.json @@ -159,6 +159,36 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "use_post", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Use POST", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "has_web_view": 0, diff --git a/frappe/core/doctype/sms_settings/sms_settings.py b/frappe/core/doctype/sms_settings/sms_settings.py index 9e25869241..588b08a229 100644 --- a/frappe/core/doctype/sms_settings/sms_settings.py +++ b/frappe/core/doctype/sms_settings/sms_settings.py @@ -65,13 +65,17 @@ def send_sms(receiver_list, msg, sender_name = '', success_msg = True): def send_via_gateway(arg): ss = frappe.get_doc('SMS Settings', 'SMS Settings') args = {ss.message_parameter: arg.get('message')} + headers={'Accept': "text/plain, text/html, */*"} for d in ss.get("parameters"): + if d.header == 1: + headers.update({d.parameter: d.value}) + continue args[d.parameter] = d.value success_list = [] for d in arg.get('receiver_list'): args[ss.receiver_parameter] = d - status = send_request(ss.sms_gateway_url, args) + status = send_request(ss.sms_gateway_url, headers, args, ss.use_post) if 200 <= status < 300: success_list.append(d) @@ -83,9 +87,12 @@ def send_via_gateway(arg): frappe.msgprint(_("SMS sent to following numbers: {0}").format("\n" + "\n".join(success_list))) -def send_request(gateway_url, params): +def send_request(gateway_url, headers, params, use_post=False): import requests - response = requests.get(gateway_url, params = params, headers={'Accept': "text/plain, text/html, */*"}) + if use_post: + response = requests.post(gateway_url, headers=headers, data=params) + else: + response = requests.get(gateway_url, headers=headers, params=params) response.raise_for_status() return response.status_code diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 448b491c69..092e9440b1 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -160,37 +160,37 @@ "unique": 0 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_first_startup", - "fieldtype": "Check", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is First Startup", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_first_startup", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Is First Startup", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1019,40 +1019,40 @@ "unique": 0 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "depends_on": "enable_two_factor_auth", - "fieldname": "bypass_2fa_for_retricted_ip_users", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Bypass Two Factor Auth for users who login from restricted IP Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "depends_on": "enable_two_factor_auth", + "fieldname": "bypass_2fa_for_retricted_ip_users", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Bypass Two Factor Auth for users who login from restricted IP Address", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, + "allow_bulk_edit": 0, + "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, @@ -1268,6 +1268,36 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "hide_footer_in_auto_email_reports", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Hide footer in auto email reports", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "has_web_view": 0, @@ -1281,8 +1311,8 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-09-13 13:26:11.045262", - "modified_by": "shri@zerodha.com", + "modified": "2017-10-15 20:29:46.700707", + "modified_by": "Administrator", "module": "Core", "name": "System Settings", "name_case": "", diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py index ef2863fd46..bd35edb880 100644 --- a/frappe/core/doctype/system_settings/system_settings.py +++ b/frappe/core/doctype/system_settings/system_settings.py @@ -14,7 +14,7 @@ from frappe.twofactor import toggle_two_factor_auth class SystemSettings(Document): def validate(self): enable_password_policy = cint(self.enable_password_policy) and True or False - minimum_password_score = cint(self.minimum_password_score) or 0 + minimum_password_score = cint(getattr(self, 'minimum_password_score', 0)) or 0 if enable_password_policy and minimum_password_score <= 0: frappe.throw(_("Please select Minimum Password Score")) elif not enable_password_policy: diff --git a/frappe/core/doctype/system_settings/test_system_settings.py b/frappe/core/doctype/system_settings/test_system_settings.py new file mode 100644 index 0000000000..82d0ddbd7c --- /dev/null +++ b/frappe/core/doctype/system_settings/test_system_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestSystemSettings(unittest.TestCase): + pass diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 13270cc352..4fa1183e74 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -42,6 +42,7 @@ class User(Document): def before_insert(self): self.flags.in_insert = True + throttle_user_creation() def validate(self): self.check_demo() @@ -976,4 +977,10 @@ def reset_otp_secret(user): enqueue(method=frappe.sendmail, queue='short', timeout=300, event=None, async=True, job_name=None, now=False, **email_args) return frappe.msgprint(_("OTP Secret has been reset. Re-registration will be required on next login.")) else: - return frappe.throw(_("OTP secret can only be reset by the Administrator.")) \ No newline at end of file + return frappe.throw(_("OTP secret can only be reset by the Administrator.")) + +def throttle_user_creation(): + if frappe.flags.in_import: + return + if frappe.db.get_creation_count('User', 60) > 60: + frappe.throw(_('Throttled')) \ No newline at end of file diff --git a/frappe/core/page/data_import_tool/data_import_main.html b/frappe/core/page/data_import_tool/data_import_main.html index db44a06e70..549bebcdfd 100644 --- a/frappe/core/page/data_import_tool/data_import_main.html +++ b/frappe/core/page/data_import_tool/data_import_main.html @@ -93,10 +93,16 @@ {%= __("Ignore encoding errors.") %} +
+ +

diff --git a/frappe/core/page/data_import_tool/data_import_tool.js b/frappe/core/page/data_import_tool/data_import_tool.js index 9d21c28348..b7e9dab79a 100644 --- a/frappe/core/page/data_import_tool/data_import_tool.js +++ b/frappe/core/page/data_import_tool/data_import_tool.js @@ -114,6 +114,7 @@ frappe.DataImportTool = Class.extend({ return { submit_after_import: me.page.main.find('[name="submit_after_import"]').prop("checked"), ignore_encoding_errors: me.page.main.find('[name="ignore_encoding_errors"]').prop("checked"), + skip_errors: me.page.main.find('[name="skip_errors"]').prop("checked"), overwrite: !me.page.main.find('[name="always_insert"]').prop("checked"), update_only: me.page.main.find('[name="update_only"]').prop("checked"), no_email: me.page.main.find('[name="no_email"]').prop("checked"), diff --git a/frappe/core/page/data_import_tool/importer.py b/frappe/core/page/data_import_tool/importer.py index d26dcfd2b1..083715b24d 100644 --- a/frappe/core/page/data_import_tool/importer.py +++ b/frappe/core/page/data_import_tool/importer.py @@ -21,7 +21,8 @@ from six import text_type, string_types @frappe.whitelist() def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, no_email=True, overwrite=None, - update_only = None, ignore_links=False, pre_process=None, via_console=False, from_data_import="No"): + update_only = None, ignore_links=False, pre_process=None, via_console=False, from_data_import="No", + skip_errors = True): """upload data""" frappe.flags.in_import = True @@ -341,13 +342,14 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, doc.submit() log('Submitted row (#%d) %s' % (row_idx + 1, as_link(doc.doctype, doc.name))) except Exception as e: - error = True - if doc: - frappe.errprint(doc if isinstance(doc, dict) else doc.as_dict()) - err_msg = frappe.local.message_log and "\n\n".join(frappe.local.message_log) or cstr(e) - log('Error for row (#%d) %s : %s' % (row_idx + 1, - len(row)>1 and row[1] or "", err_msg)) - frappe.errprint(frappe.get_traceback()) + if not skip_errors: + error = True + if doc: + frappe.errprint(doc if isinstance(doc, dict) else doc.as_dict()) + err_msg = frappe.local.message_log and "\n\n".join(frappe.local.message_log) or cstr(e) + log('Error for row (#%d) %s : %s' % (row_idx + 1, + len(row)>1 and row[1] or "", err_msg)) + frappe.errprint(frappe.get_traceback()) finally: frappe.local.message_log = [] diff --git a/frappe/core/page/usage_info/usage_info.html b/frappe/core/page/usage_info/usage_info.html index b108f1e7d5..5fd564335c 100644 --- a/frappe/core/page/usage_info/usage_info.html +++ b/frappe/core/page/usage_info/usage_info.html @@ -1,114 +1,75 @@ -

+
{% if limits.expiry %} -

{{ __("Expires in {0} days", [days_to_expiry]) }}

- {{ __("Renew before: {0}", [expires_on]) }} -

+
+

{{ __("You have {0} days left in your trial", [days_to_expiry]) }}

+ + {% if limits.upgrade_url %} +

Upgrade to a premium plan with more users, storage and priority support.

+ + {% endif %} +
{% endif %} {% if limits.users %} {% var users_percent = ((enabled_users / limits.users) * 100); %} -

{{ __("Users") }}

+
+

{{ __("Users") }}

-
-
-
-
+
+
+
+
- - - - - - - - - - - - - - - -
{{ __("Current Users") }}{{ __("Max Users") }}{{ __("Remaining") }}
{%= enabled_users %}{%= limits.users %}{%= limits.users - enabled_users %}
-
+

{%= enabled_users %} out of {%= limits.users %} enabled

+
{% endif %} {% if limits.emails %} -

{{ __("Emails sent this month") }}

+
+

{{ __("Emails") }}

{% var email_percent = (( emails_sent / limits.emails ) * 100); %} {% var emails_remaining = (limits.emails - emails_sent) %} -
+
- - - - - - - - - - - - - - - -
{{ __("Emails Sent") }}{{ __("Max Emails") }}{{ __("Remaining") }}
{%= emails_sent %}{%= limits.emails %}{%= emails_remaining %}
-
+

{%= emails_sent %} out of {%= limits.emails %} sent this month

+
{% endif %} {% if limits.space %} -

{{ __("Space usage") }}

+
+

{{ __("Space") }}

{% var database_percent = ((limits.space_usage.database_size / limits.space) * 100); %} {% var files_percent = ((limits.space_usage.files_size / limits.space) * 100); %} {% var backup_percent = ((limits.space_usage.backup_size / limits.space) * 100); %} -
-
-
-
-
-
-
+
+
+
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ __("Type") }} {{ __("Size (MB)") }}
{{ __("Database Size") }}{%= limits.space_usage.database_size %} MB
{{ __("Files Size") }}{%= limits.space_usage.files_size %} MB
{{ __("Backup Size") }}{%= limits.space_usage.backup_size %} MB
{{ __("Total") }}{%= limits.space_usage.total %} MB
{{ __("Remaining") }} - {%= flt(limits.space - limits.space_usage.total, 2) %} MB
+ + {{ __("Database Size:") }} {%= limits.space_usage.files_size %} MB + + + {{ __("Files Size:") }} {%= limits.space_usage.files_size %} MB + + + {{ __("Backup Size:") }} {%= limits.space_usage.backup_size %} MB + + +

+ + {%= flt(limits.space - limits.space_usage.total, 2) %} MB + available out of + {%= limits.space %} MB +

+
{% endif %}
diff --git a/frappe/core/page/usage_info/usage_info.js b/frappe/core/page/usage_info/usage_info.js index 1f2409980e..5137eeac6a 100644 --- a/frappe/core/page/usage_info/usage_info.js +++ b/frappe/core/page/usage_info/usage_info.js @@ -18,12 +18,9 @@ frappe.pages['usage-info'].on_page_load = function(wrapper) { $(frappe.render_template("usage_info", usage_info)).appendTo(page.main); var btn_text = usage_info.limits.users == 1 ? __("Upgrade") : __("Renew / Upgrade"); - - if(usage_info.upgrade_url) { - page.set_primary_action(btn_text, function() { - window.open(usage_info.upgrade_url); - }); - } + $(page.main).find('.btn-primary').html(btn_text).on('click', () => { + window.open(usage_info.upgrade_url); + }); } }); diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 6eb3eef544..a94608f25d 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -111,6 +111,10 @@ def create_custom_fields(custom_fields): :param custom_fields: example `{'Sales Invoice': [dict(fieldname='test')]}`''' for doctype, fields in custom_fields.items(): + if isinstance(fields, dict): + # only one field + fields = [fields] + for df in fields: field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": df["fieldname"]}) if not field: diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 085e632545..70251711f3 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -66,7 +66,7 @@ docfield_properties = { allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'), ('Text', 'Data'), ('Text', 'Text Editor', 'Code', 'Signature'), ('Data', 'Select'), - ('Text', 'Small Text')) + ('Text', 'Small Text'), ('Text', 'Data', 'Barcode')) allowed_fieldtype_for_options_change = ('Read Only', 'HTML', 'Select',) diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index 4b9ab79eaa..8dc0a6a3fa 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -94,7 +94,7 @@ "no_copy": 0, "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nText\nText Editor\nTime", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nText\nText Editor\nTime", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -1202,7 +1202,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-07-06 17:24:03.665171", + "modified": "2017-10-11 06:45:20.172291", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", diff --git a/frappe/data_migration/__init__.py b/frappe/data_migration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/data_migration/doctype/__init__.py b/frappe/data_migration/doctype/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/data_migration/doctype/data_migration_connector/__init__.py b/frappe/data_migration/doctype/data_migration_connector/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/data_migration/doctype/data_migration_connector/connectors/__init__.py b/frappe/data_migration/doctype/data_migration_connector/connectors/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/data_migration/doctype/data_migration_connector/connectors/base.py b/frappe/data_migration/doctype/data_migration_connector/connectors/base.py new file mode 100644 index 0000000000..e8e533e372 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_connector/connectors/base.py @@ -0,0 +1,24 @@ +from six import with_metaclass +from abc import ABCMeta, abstractmethod +from frappe.utils.password import get_decrypted_password + +class BaseConnection(with_metaclass(ABCMeta)): + + @abstractmethod + def get(self): + pass + + @abstractmethod + def insert(self): + pass + + @abstractmethod + def update(self): + pass + + @abstractmethod + def delete(self): + pass + + def get_password(self): + return get_decrypted_password('Data Migration Connector', self.connector.name) \ No newline at end of file diff --git a/frappe/data_migration/doctype/data_migration_connector/connectors/frappe_connection.py b/frappe/data_migration/doctype/data_migration_connector/connectors/frappe_connection.py new file mode 100644 index 0000000000..6ee41afdf2 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_connector/connectors/frappe_connection.py @@ -0,0 +1,29 @@ +from __future__ import unicode_literals +import frappe +from frappe.frappeclient import FrappeClient +from .base import BaseConnection + +class FrappeConnection(BaseConnection): + def __init__(self, connector): + self.connector = connector + self.connection = FrappeClient(self.connector.hostname, + self.connector.username, self.get_password()) + self.name_field = 'name' + + def insert(self, doctype, doc): + doc = frappe._dict(doc) + doc.doctype = doctype + return self.connection.insert(doc) + + def update(self, doctype, doc, migration_id): + doc = frappe._dict(doc) + doc.doctype = doctype + doc.name = migration_id + return self.connection.update(doc) + + def delete(self, doctype, migration_id): + return self.connection.delete(doctype, migration_id) + + def get(self, doctype, fields='"*"', filters=None, start=0, page_length=20): + return self.connection.get_list(doctype, fields=fields, filters=filters, + limit_start=start, limit_page_length=page_length) diff --git a/frappe/data_migration/doctype/data_migration_connector/connectors/postgres.py b/frappe/data_migration/doctype/data_migration_connector/connectors/postgres.py new file mode 100644 index 0000000000..9c3e2af64d --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_connector/connectors/postgres.py @@ -0,0 +1,43 @@ +from __future__ import unicode_literals +import frappe, psycopg2 +from .base import BaseConnection + +class PostGresConnection(BaseConnection): + def __init__(self, properties): + self.__dict__.update(properties) + self._connector = psycopg2.connect("host='{0}' dbname='{1}' user='{2}' password='{3}'".format(self.hostname, + self.database_name, self.username, self.password)) + self.cursor = self._connector.cursor() + + def get_objects(self, object_type, condition, selection): + if not condition: + condition = '' + else: + condition = ' WHERE ' + condition + self.cursor.execute('SELECT {0} FROM {1}{2}'.format(selection, object_type, condition)) + raw_data = self.cursor.fetchall() + data = [] + for r in raw_data: + row_dict = frappe._dict({}) + for i, value in enumerate(r): + row_dict[self.cursor.description[i][0]] = value + data.append(row_dict) + + return data + + def get_join_objects(self, object_type, field, primary_key): + """ + field.formula 's first line will be list of tables that needs to be linked to fetch an item + The subsequent lines that follows will contain one to one mapping across tables keys + """ + condition = "" + key_mapping = field.formula.split('\n') + obj_type = key_mapping[0] + selection = field.source_fieldname + + for d in key_mapping[1:]: + condition += d + ' AND ' + + condition += str(object_type) + ".id=" + str(primary_key) + + return self.get_objects(obj_type, condition, selection) diff --git a/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.js b/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.js new file mode 100644 index 0000000000..f4f0d9f474 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.js @@ -0,0 +1,8 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Data Migration Connector', { + refresh: function() { + + } +}); diff --git a/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.json b/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.json new file mode 100644 index 0000000000..0a46c464e5 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.json @@ -0,0 +1,275 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 1, + "autoname": "field:connector_name", + "beta": 1, + "creation": "2017-08-11 05:03:27.091416", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "connector_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Connector Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "connector_type", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Connector Type", + "length": 0, + "no_copy": 0, + "options": "Frappe\nPostgres", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "python_module", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Python Module", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "localhost", + "fieldname": "hostname", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Hostname", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "database_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Database Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "username", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Username", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "password", + "fieldtype": "Password", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Password", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-08 14:34:30.603690", + "modified_by": "Administrator", + "module": "Data Migration", + "name": "Data Migration Connector", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.py b/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.py new file mode 100644 index 0000000000..13094bb54d --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe import _ +from .connectors.postgres import PostGresConnection +from .connectors.frappe_connection import FrappeConnection + +class DataMigrationConnector(Document): + def validate(self): + if not (self.python_module or self.connector_type): + frappe.throw(_('Enter python module or select connector type')) + + if self.python_module: + try: + frappe.get_module(self.python_module) + except: + frappe.throw(frappe._('Invalid module path')) + + def get_connection(self): + if self.python_module: + module = frappe.get_module(self.python_module) + return module.get_connection(self) + else: + if self.connector_type == 'Frappe': + self.connection = FrappeConnection(self) + elif self.connector_type == 'PostGres': + self.connection = PostGresConnection(self.as_dict()) + + return self.connection + + def get_objects(self, object_type, condition=None, selection="*"): + return self.connector.get_objects(object_type, condition, selection) + + def get_join_objects(self, object_type, join_type, primary_key): + return self.connector.get_join_objects(object_type, join_type, primary_key) diff --git a/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.js b/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.js new file mode 100644 index 0000000000..b933deb433 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Data Migration Connector", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Data Migration Connector + () => frappe.tests.make('Data Migration Connector', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.py b/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.py new file mode 100644 index 0000000000..a6e30fbe44 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals +import unittest + +class TestDataMigrationConnector(unittest.TestCase): + pass diff --git a/frappe/data_migration/doctype/data_migration_mapping/__init__.py b/frappe/data_migration/doctype/data_migration_mapping/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.js b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.js new file mode 100644 index 0000000000..6c99b9a54d --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.js @@ -0,0 +1,8 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Data Migration Mapping', { + refresh: function() { + + } +}); diff --git a/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.json b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.json new file mode 100644 index 0000000000..998abdf6ca --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.json @@ -0,0 +1,456 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 1, + "autoname": "field:mapping_name", + "beta": 1, + "creation": "2017-08-11 05:11:49.975801", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "mapping_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Mapping Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "remote_objectname", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Remote Objectname", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "remote_primary_key", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Remote Primary Key", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "local_doctype", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Local DocType", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "local_primary_key", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Local Primary Key", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_5", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "mapping_type", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Mapping Type", + "length": 0, + "no_copy": 0, + "options": "Push\nPull\nSync", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "10", + "fieldname": "page_length", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Page Length", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "migration_id_field", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Migration ID Field", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "mapping", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Mapping", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "fields", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Field Maps", + "length": 0, + "no_copy": 0, + "options": "Data Migration Mapping Detail", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "condition_detail", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Condition Detail", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "condition", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Condition", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-09-27 18:06:43.275207", + "modified_by": "Administrator", + "module": "Data Migration", + "name": "Data Migration Mapping", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py new file mode 100644 index 0000000000..ca20162f42 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class DataMigrationMapping(Document): + def get_filters(self): + if self.condition: + return frappe.safe_eval(self.condition, dict(frappe=frappe)) + + def get_fields(self): + fields = [] + for f in self.fields: + if not (f.local_fieldname[0] in ('"', "'") or f.local_fieldname.startswith('eval:')): + fields.append(f.local_fieldname) + + if frappe.db.has_column(self.local_doctype, self.migration_id_field): + fields.append(self.migration_id_field) + + if 'name' not in fields: + fields.append('name') + + return fields + + def get_mapped_record(self, doc): + mapped = frappe._dict() + + key_fieldname = 'remote_fieldname' + value_fieldname = 'local_fieldname' + + if self.mapping_type == 'Pull': + key_fieldname, value_fieldname = value_fieldname, key_fieldname + + for field_map in self.fields: + if not field_map.is_child_table: + value = get_value_from_fieldname(field_map, value_fieldname, doc) + mapped[field_map.get(key_fieldname)] = value + else: + mapping_name = field_map.child_table_mapping + value = get_mapped_child_records(mapping_name, doc.get(field_map.get(value_fieldname))) + mapped[field_map.get(key_fieldname)] = value + return mapped + +def get_mapped_child_records(mapping_name, child_docs): + mapped_child_docs = [] + mapping = frappe.get_doc('Data Migration Mapping', mapping_name) + for child_doc in child_docs: + mapped_child_docs.append(mapping.get_mapped_record(child_doc)) + + return mapped_child_docs + +def get_value_from_fieldname(field_map, fieldname_field, doc): + field_name = field_map.get(fieldname_field) + + if field_name.startswith('eval:'): + value = frappe.safe_eval(field_name[5:], dict(frappe=frappe)) + elif field_name[0] in ('"', "'"): + value = field_name[1:-1] + else: + value = doc.get(field_name) + return value diff --git a/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.js b/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.js new file mode 100644 index 0000000000..e6966ef131 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Data Migration Mapping", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Data Migration Mapping + () => frappe.tests.make('Data Migration Mapping', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.py b/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.py new file mode 100644 index 0000000000..e6f0ce2796 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals +import unittest + +class TestDataMigrationMapping(unittest.TestCase): + pass diff --git a/frappe/data_migration/doctype/data_migration_mapping_detail/__init__.py b/frappe/data_migration/doctype/data_migration_mapping_detail/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.json b/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.json new file mode 100644 index 0000000000..ede9213f14 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.json @@ -0,0 +1,163 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-08-11 05:09:10.900237", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "remote_fieldname", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Remote Fieldname", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "local_fieldname", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Local Fieldname", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_child_table", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Is Child Table", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "is_child_table", + "fieldname": "child_table_mapping", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Child Table Mapping", + "length": 0, + "no_copy": 0, + "options": "Data Migration Mapping", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-09-28 17:13:31.337005", + "modified_by": "Administrator", + "module": "Data Migration", + "name": "Data Migration Mapping Detail", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.py b/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.py new file mode 100644 index 0000000000..1ccdf76eed --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class DataMigrationMappingDetail(Document): + pass diff --git a/frappe/data_migration/doctype/data_migration_plan/__init__.py b/frappe/data_migration/doctype/data_migration_plan/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.js b/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.js new file mode 100644 index 0000000000..935a227e79 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.js @@ -0,0 +1,8 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Data Migration Plan', { + refresh: function() { + + } +}); diff --git a/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.json b/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.json new file mode 100644 index 0000000000..8feff93286 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.json @@ -0,0 +1,155 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:plan_name", + "beta": 0, + "creation": "2017-08-11 05:15:51.482165", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "plan_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Plan Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "module", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Module", + "length": 0, + "no_copy": 0, + "options": "Module Def", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "mappings", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Mappings", + "length": 0, + "no_copy": 0, + "options": "Data Migration Plan Mapping", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-09-13 15:47:26.336541", + "modified_by": "prateeksha@erpnext.com", + "module": "Data Migration", + "name": "Data Migration Plan", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.py b/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.py new file mode 100644 index 0000000000..313ca5943e --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.modules import get_module_path, scrub_dt_dn +from frappe.modules.export_file import export_to_files, create_init_py +from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe.model.document import Document + +class DataMigrationPlan(Document): + + def on_update(self): + # update custom fields in mappings + self.make_custom_fields_for_mappings() + + if frappe.flags.in_import or frappe.flags.in_test: + return + + if frappe.local.conf.get('developer_mode'): + record_list =[['Data Migration Plan', self.name]] + + for m in self.mappings: + record_list.append(['Data Migration Mapping', m.mapping]) + + export_to_files(record_list=record_list, record_module=self.module) + + for m in self.mappings: + dt, dn = scrub_dt_dn('Data Migration Mapping', m.mapping) + create_init_py(get_module_path(self.module), dt, dn) + + def make_custom_fields_for_mappings(self): + label = self.name + ' ID' + fieldname = frappe.scrub(label) + + df = { + 'label': label, + 'fieldname': fieldname, + 'fieldtype': 'Data', + 'hidden': 1, + 'read_only': 1, + 'unique': 1 + } + + for m in self.mappings: + mapping = frappe.get_doc('Data Migration Mapping', m.mapping) + create_custom_field(mapping.local_doctype, df) + mapping.migration_id_field = fieldname + mapping.save() + + # Create custom field in Deleted Document + create_custom_field('Deleted Document', df) + + def pre_process_doc(self, mapping_name, doc): + module = self.get_mapping_module(mapping_name) + + if module and hasattr(module, 'pre_process'): + return module.pre_process(doc) + return doc + + def post_process_doc(self, mapping_name, local_doc=None, remote_doc=None): + module = self.get_mapping_module(mapping_name) + + if module and hasattr(module, 'post_process'): + return module.post_process(local_doc=local_doc, remote_doc=remote_doc) + + def get_mapping_module(self, mapping_name): + try: + module_def = frappe.get_doc("Module Def", self.module) + module = frappe.get_module('{app}.{module}.data_migration_mapping.{mapping_name}'.format( + app= module_def.app_name, + module=frappe.scrub(self.module), + mapping_name=frappe.scrub(mapping_name) + )) + return module + except ImportError: + return None diff --git a/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.js b/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.js new file mode 100644 index 0000000000..9943cd6ec1 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Data Migration Plan", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Data Migration Plan + () => frappe.tests.make('Data Migration Plan', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.py b/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.py new file mode 100644 index 0000000000..3a33039c3d --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals +import unittest + +class TestDataMigrationPlan(unittest.TestCase): + pass diff --git a/frappe/data_migration/doctype/data_migration_plan_mapping/__init__.py b/frappe/data_migration/doctype/data_migration_plan_mapping/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.json b/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.json new file mode 100644 index 0000000000..5acf014715 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.json @@ -0,0 +1,103 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2017-08-11 05:15:38.390831", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "mapping", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Mapping", + "length": 0, + "no_copy": 0, + "options": "Data Migration Mapping", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fieldname": "enabled", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Enabled", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-09-20 21:43:04.908650", + "modified_by": "Administrator", + "module": "Data Migration", + "name": "Data Migration Plan Mapping", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.py b/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.py new file mode 100644 index 0000000000..85f879069c --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class DataMigrationPlanMapping(Document): + pass diff --git a/frappe/data_migration/doctype/data_migration_run/__init__.py b/frappe/data_migration/doctype/data_migration_run/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/data_migration/doctype/data_migration_run/data_migration_run.js b/frappe/data_migration/doctype/data_migration_run/data_migration_run.js new file mode 100644 index 0000000000..82323c62f1 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_run/data_migration_run.js @@ -0,0 +1,14 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Data Migration Run', { + refresh: function(frm) { + if (frm.doc.status !== 'Success') { + frm.add_custom_button(__('Run'), () => frm.call('run')); + } + if (frm.doc.status === 'Started') { + frm.dashboard.add_progress(__('Percent Complete'), frm.doc.percent_complete, + __('Currently updating {0}', [frm.doc.current_mapping])); + } + } +}); diff --git a/frappe/data_migration/doctype/data_migration_run/data_migration_run.json b/frappe/data_migration/doctype/data_migration_run/data_migration_run.json new file mode 100644 index 0000000000..dad75d0a2a --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_run/data_migration_run.json @@ -0,0 +1,671 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-09-11 12:55:27.597728", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "data_migration_plan", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Data Migration Plan", + "length": 0, + "no_copy": 0, + "options": "Data Migration Plan", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "data_migration_connector", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Data Migration Connector", + "length": 0, + "no_copy": 0, + "options": "Data Migration Connector", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Pending", + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Status", + "length": 0, + "no_copy": 1, + "options": "Pending\nStarted\nPartial Success\nSuccess\nFail\nError", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "current_mapping", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Current Mapping", + "length": 0, + "no_copy": 1, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "current_mapping_start", + "fieldtype": "Int", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Current Mapping Start", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "current_mapping_delete_start", + "fieldtype": "Int", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Current Mapping Delete Start", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "current_mapping_type", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Current Mapping Type", + "length": 0, + "no_copy": 0, + "options": "Push\nPull", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.status !== 'Pending')", + "fieldname": "current_mapping_action", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Current Mapping Action", + "length": 0, + "no_copy": 1, + "options": "Insert\nDelete", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "total_pages", + "fieldtype": "Int", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Pages", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "percent_complete", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Percent Complete", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.status !== 'Pending')", + "fieldname": "logs_sb", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Logs", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "push_insert", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Push Insert", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "push_update", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Push Update", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "push_delete", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Push Delete", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "push_failed", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Push Failed", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_16", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "pull_insert", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Pull Insert", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "pull_update", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Pull Update", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "pull_failed", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Pull Failed", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.failed_log !== '[]'", + "fieldname": "log", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Log", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-02 05:12:16.094991", + "modified_by": "Administrator", + "module": "Data Migration", + "name": "Data Migration Run", + "name_case": "", + "owner": "faris@erpnext.com", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py new file mode 100644 index 0000000000..64c968b6cc --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py @@ -0,0 +1,476 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe, json, math +from frappe.model.document import Document +from frappe import _ + +class DataMigrationRun(Document): + + def validate(self): + exists = frappe.db.exists('Data Migration Run', dict( + status=('in', ['Fail', 'Error']), + name=('!=', self.name) + )) + if exists: + frappe.throw(_('There are failed runs with the same Data Migration Plan')) + + def run(self): + self.begin() + if self.total_pages > 0: + self.enqueue_next_mapping() + else: + self.complete() + + def enqueue_next_mapping(self): + next_mapping_name = self.get_next_mapping_name() + if next_mapping_name: + next_mapping = self.get_mapping(next_mapping_name) + self.db_set(dict( + current_mapping = next_mapping.name, + current_mapping_start = 0, + current_mapping_delete_start = 0, + current_mapping_action = 'Insert' + ), notify=True, commit=True) + frappe.enqueue_doc(self.doctype, self.name, 'run_current_mapping', now=frappe.flags.in_test) + else: + self.complete() + + def enqueue_next_page(self): + mapping = self.get_mapping(self.current_mapping) + fields = dict( + percent_complete = self.percent_complete + (100.0 / self.total_pages) + ) + if self.current_mapping_action == 'Insert': + start = self.current_mapping_start + mapping.page_length + fields['current_mapping_start'] = start + elif self.current_mapping_action == 'Delete': + delete_start = self.current_mapping_delete_start + mapping.page_length + fields['current_mapping_delete_start'] = delete_start + + self.db_set(fields, notify=True, commit=True) + frappe.enqueue_doc(self.doctype, self.name, 'run_current_mapping', now=frappe.flags.in_test) + + def run_current_mapping(self): + try: + mapping = self.get_mapping(self.current_mapping) + + if mapping.mapping_type == 'Push': + done = self.push() + elif mapping.mapping_type == 'Pull': + done = self.pull() + + if done: + self.enqueue_next_mapping() + else: + self.enqueue_next_page() + + except Exception as e: + self.db_set('status', 'Error', notify=True, commit=True) + print('Data Migration Run failed') + print(frappe.get_traceback()) + raise e + + def get_last_modified_condition(self): + last_run_timestamp = frappe.db.get_value('Data Migration Run', dict( + data_migration_plan=self.data_migration_plan, + name=('!=', self.name) + ), 'modified') + if last_run_timestamp: + condition = dict(modified=('>', last_run_timestamp)) + else: + condition = {} + return condition + + def begin(self): + plan_active_mappings = [m for m in self.get_plan().mappings if m.enabled] + self.mappings = [frappe.get_doc( + 'Data Migration Mapping', m.mapping) for m in plan_active_mappings] + + total_pages = 0 + for m in [mapping for mapping in self.mappings]: + if m.mapping_type == 'Push': + count = float(self.get_count(m)) + page_count = math.ceil(count / m.page_length) + total_pages += page_count + if m.mapping_type == 'Pull': + total_pages += 10 + + self.db_set(dict( + status = 'Started', + current_mapping = None, + current_mapping_start = 0, + current_mapping_delete_start = 0, + percent_complete = 0, + current_mapping_action = 'Insert', + total_pages = total_pages + ), notify=True, commit=True) + + def complete(self): + fields = dict() + + push_failed = self.get_log('push_failed', []) + pull_failed = self.get_log('pull_failed', []) + + if push_failed or pull_failed: + fields['status'] = 'Partial Success' + else: + fields['status'] = 'Success' + fields['percent_complete'] = 100 + + self.db_set(fields, notify=True, commit=True) + + def get_plan(self): + if not hasattr(self, 'plan'): + self.plan = frappe.get_doc('Data Migration Plan', self.data_migration_plan) + return self.plan + + def get_mapping(self, mapping_name): + if hasattr(self, 'mappings'): + for m in self.mappings: + if m.name == mapping_name: + return m + return frappe.get_doc('Data Migration Mapping', mapping_name) + + def get_next_mapping_name(self): + mappings = [m for m in self.get_plan().mappings if m.enabled] + if not self.current_mapping: + # first + return mappings[0].mapping + for i, d in enumerate(mappings): + if i == len(mappings) - 1: + # last + return None + if d.mapping == self.current_mapping: + return mappings[i+1].mapping + + raise frappe.ValidationError('Mapping Broken') + + def get_data(self, filters): + mapping = self.get_mapping(self.current_mapping) + or_filters = self.get_or_filters(mapping) + start = self.current_mapping_start + + data = [] + doclist = frappe.get_all(mapping.local_doctype, + filters=filters, or_filters=or_filters, + start=start, page_length=mapping.page_length) + + for d in doclist: + doc = frappe.get_doc(mapping.local_doctype, d['name']) + data.append(doc) + return data + + def get_new_local_data(self): + '''Fetch newly inserted local data using `frappe.get_all`. Used during Push''' + mapping = self.get_mapping(self.current_mapping) + filters = mapping.get_filters() or {} + + # new docs dont have migration field set + filters.update({ + mapping.migration_id_field: '' + }) + + return self.get_data(filters) + + def get_updated_local_data(self): + '''Fetch local updated data using `frappe.get_all`. Used during Push''' + mapping = self.get_mapping(self.current_mapping) + filters = mapping.get_filters() or {} + + # existing docs must have migration field set + filters.update({ + mapping.migration_id_field: ('!=', '') + }) + + return self.get_data(filters) + + def get_deleted_local_data(self): + '''Fetch local deleted data using `frappe.get_all`. Used during Push''' + mapping = self.get_mapping(self.current_mapping) + or_filters = self.get_or_filters(mapping) + filters = dict( + deleted_doctype=mapping.local_doctype + ) + + data = frappe.get_all('Deleted Document', fields=['data'], + filters=filters, or_filters=or_filters) + + _data = [] + for d in data: + doc = json.loads(d.data) + if doc.get(mapping.migration_id_field): + doc['_deleted_document_name'] = d.name + _data.append(doc) + + return _data + + def get_remote_data(self): + '''Fetch data from remote using `connection.get`. Used during Pull''' + mapping = self.get_mapping(self.current_mapping) + start = self.current_mapping_start + filters = mapping.get_filters() or {} + connection = self.get_connection() + + return connection.get(mapping.remote_objectname, + fields=["*"], filters=filters, start=start, + page_length=mapping.page_length) + + def get_count(self, mapping): + filters = mapping.get_filters() or {} + or_filters = self.get_or_filters(mapping) + + to_insert = frappe.get_all(mapping.local_doctype, ['count(name) as total'], + filters=filters, or_filters=or_filters)[0].total + + to_delete = frappe.get_all('Deleted Document', ['count(name) as total'], + filters={'deleted_doctype': mapping.local_doctype}, or_filters=or_filters)[0].total + + return to_insert + to_delete + + def get_or_filters(self, mapping): + or_filters = self.get_last_modified_condition() + + # include docs whose migration_id_field is not set + or_filters.update({ + mapping.migration_id_field: ('=', '') + }) + + return or_filters + + def get_connection(self): + if not hasattr(self, 'connection'): + self.connection = frappe.get_doc('Data Migration Connector', + self.data_migration_connector).get_connection() + + return self.connection + + def push(self): + self.db_set('current_mapping_type', 'Push') + done = True + + if self.current_mapping_action == 'Insert': + done = self._push_insert() + + elif self.current_mapping_action == 'Update': + done = self._push_update() + + elif self.current_mapping_action == 'Delete': + done = self._push_delete() + + return done + + def _push_insert(self): + '''Inserts new local docs on remote''' + mapping = self.get_mapping(self.current_mapping) + connection = self.get_connection() + data = self.get_new_local_data() + + push_insert = self.get_log('push_insert', 0) + push_failed = self.get_log('push_failed', []) + + for d in data: + # pre process before insert + doc = self.pre_process_doc(d) + doc = mapping.get_mapped_record(doc) + + try: + response_doc = connection.insert(mapping.remote_objectname, doc) + frappe.db.set_value(mapping.local_doctype, d.name, + mapping.migration_id_field, response_doc[connection.name_field], + update_modified=False) + frappe.db.commit() + self.set_log('push_insert', push_insert + 1) + # post process after insert + self.post_process_doc(local_doc=d, remote_doc=response_doc) + except Exception: + push_failed.append(d.as_json()) + self.set_log('push_failed', push_failed) + + # update page_start + self.db_set('current_mapping_start', + self.current_mapping_start + mapping.page_length) + + if len(data) < mapping.page_length: + # done, no more new data to insert + self.db_set({ + 'current_mapping_action': 'Update', + 'current_mapping_start': 0 + }) + # not done with this mapping + return False + + def _push_update(self): + '''Updates local modified docs on remote''' + mapping = self.get_mapping(self.current_mapping) + connection = self.get_connection() + data = self.get_updated_local_data() + + push_update = self.get_log('push_update', 0) + push_failed = self.get_log('push_failed', []) + + for d in data: + migration_id_value = d.get(mapping.migration_id_field) + # pre process before update + doc = self.pre_process_doc(d) + doc = mapping.get_mapped_record(doc) + try: + response_doc = connection.update(mapping.remote_objectname, doc, migration_id_value) + self.set_log('push_update', push_update + 1) + # post process after update + self.post_process_doc(local_doc=d, remote_doc=response_doc) + except Exception: + push_failed.append(d.as_json()) + self.set_log('push_failed', push_failed) + + # update page_start + self.db_set('current_mapping_start', + self.current_mapping_start + mapping.page_length) + + if len(data) < mapping.page_length: + # done, no more data to update + self.db_set({ + 'current_mapping_action': 'Delete', + 'current_mapping_start': 0 + }) + # not done with this mapping + return False + + def _push_delete(self): + '''Deletes docs deleted from local on remote''' + mapping = self.get_mapping(self.current_mapping) + connection = self.get_connection() + data = self.get_deleted_local_data() + + push_delete = self.get_log('push_delete', 0) + push_failed = self.get_log('push_failed', []) + + for d in data: + # Deleted Document also has a custom field for migration_id + migration_id_value = d.get(mapping.migration_id_field) + # pre process before update + self.pre_process_doc(d) + try: + response_doc = connection.delete(mapping.remote_objectname, migration_id_value) + self.set_log('push_delete', push_delete + 1) + # post process only when action is success + self.post_process_doc(local_doc=d, remote_doc=response_doc) + except Exception: + push_failed.append(d.as_json()) + self.set_log('push_failed', push_failed) + + # update page_start + self.db_set('current_mapping_start', + self.current_mapping_start + mapping.page_length) + + if len(data) < mapping.page_length: + # done, no more new data to delete + # done with this mapping + return True + + def pull(self): + self.db_set('current_mapping_type', 'Pull') + + connection = self.get_connection() + mapping = self.get_mapping(self.current_mapping) + data = self.get_remote_data() + + pull_insert = self.get_log('pull_insert', 0) + pull_update = self.get_log('pull_update', 0) + pull_failed = self.get_log('pull_failed', []) + + def get_migration_id_value(source, key): + value = None + try: + value = source[key] + except: + value = getattr(source, key) + return value + + for d in data: + migration_id_value = get_migration_id_value(d, connection.name_field) + doc = self.pre_process_doc(d) + doc = mapping.get_mapped_record(doc) + + if migration_id_value: + if not local_doc_exists(mapping, migration_id_value): + # insert new local doc + local_doc = insert_local_doc(mapping, doc) + + self.set_log('pull_insert', pull_insert + 1) + # set migration id + frappe.db.set_value(mapping.local_doctype, local_doc.name, + mapping.migration_id_field, migration_id_value, + update_modified=False) + frappe.db.commit() + else: + # update doc + local_doc = update_local_doc(mapping, doc, migration_id_value) + self.set_log('pull_update', pull_update + 1) + + if local_doc: + # post process doc after success + self.post_process_doc(remote_doc=d, local_doc=local_doc) + else: + # failed, append to log + pull_failed.append(d) + self.set_log('pull_failed', pull_failed) + + if len(data) < mapping.page_length: + # last page, done with pull + return True + + def pre_process_doc(self, doc): + plan = self.get_plan() + doc = plan.pre_process_doc(self.current_mapping, doc) + return doc + + def post_process_doc(self, local_doc=None, remote_doc=None): + plan = self.get_plan() + doc = plan.post_process_doc(self.current_mapping, local_doc=local_doc, remote_doc=remote_doc) + return doc + + def set_log(self, key, value): + value = json.dumps(value) if '_failed' in key else value + self.db_set(key, value) + + def get_log(self, key, default=None): + value = self.db_get(key) + if '_failed' in key: + if not value: value = json.dumps(default) + value = json.loads(value) + return value or default + +def insert_local_doc(mapping, doc): + try: + # insert new doc + if not doc.doctype: + doc.doctype = mapping.local_doctype + doc = frappe.get_doc(doc).insert() + return doc + except Exception: + print('Data Migration Run failed: Error in Pull insert') + print(frappe.get_traceback()) + return None + +def update_local_doc(mapping, remote_doc, migration_id_value): + try: + # migration id value is set in migration_id_field in mapping.local_doctype + docname = frappe.db.get_value(mapping.local_doctype, + filters={ mapping.migration_id_field: migration_id_value }) + + doc = frappe.get_doc(mapping.local_doctype, docname) + doc.update(remote_doc) + doc.save() + return doc + except Exception: + print('Data Migration Run failed: Error in Pull update') + print(frappe.get_traceback()) + return None + +def local_doc_exists(mapping, migration_id_value): + return frappe.db.exists(mapping.local_doctype, { + mapping.migration_id_field: migration_id_value + }) diff --git a/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.js b/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.js new file mode 100644 index 0000000000..04a127f730 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Data Migration Run", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Data Migration Run + () => frappe.tests.make('Data Migration Run', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py b/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py new file mode 100644 index 0000000000..189b4cc228 --- /dev/null +++ b/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals +import frappe, unittest + +class TestDataMigrationRun(unittest.TestCase): + def test_run(self): + create_plan() + + description = 'Data migration todo' + new_todo = frappe.get_doc({ + 'doctype': 'ToDo', + 'description': description + }).insert() + + event_subject = 'Data migration event' + frappe.get_doc(dict( + doctype='Event', + subject=event_subject, + repeat_on='Every Month', + starts_on=frappe.utils.now_datetime() + )).insert() + + run = frappe.get_doc({ + 'doctype': 'Data Migration Run', + 'data_migration_plan': 'ToDo Sync', + 'data_migration_connector': 'Local Connector' + }).insert() + + run.run() + self.assertEqual(run.db_get('status'), 'Success') + + self.assertEqual(run.db_get('push_insert'), 1) + self.assertEqual(run.db_get('pull_insert'), 1) + + todo = frappe.get_doc('ToDo', new_todo.name) + self.assertTrue(todo.todo_sync_id) + + # Pushed Event + event = frappe.get_doc('Event', todo.todo_sync_id) + self.assertEqual(event.subject, description) + + # Pulled ToDo + created_todo = frappe.get_doc('ToDo', {'description': event_subject}) + self.assertEqual(created_todo.description, event_subject) + + todo_list = frappe.get_list('ToDo', filters={'description': 'Data migration todo'}, fields=['name']) + todo_name = todo_list[0].name + + todo = frappe.get_doc('ToDo', todo_name) + todo.description = 'Data migration todo updated' + todo.save() + + run = frappe.get_doc({ + 'doctype': 'Data Migration Run', + 'data_migration_plan': 'ToDo Sync', + 'data_migration_connector': 'Local Connector' + }).insert() + + run.run() + + # Update + self.assertEqual(run.db_get('status'), 'Success') + self.assertEqual(run.db_get('push_update'), 1) + self.assertEqual(run.db_get('pull_update'), 1) + +def create_plan(): + frappe.get_doc({ + 'doctype': 'Data Migration Mapping', + 'mapping_name': 'Todo to Event', + 'remote_objectname': 'Event', + 'remote_primary_key': 'name', + 'mapping_type': 'Push', + 'local_doctype': 'ToDo', + 'fields': [ + { 'remote_fieldname': 'subject', 'local_fieldname': 'description' }, + { 'remote_fieldname': 'starts_on', 'local_fieldname': 'eval:frappe.utils.get_datetime_str(frappe.utils.get_datetime())' } + ] + }).insert() + + frappe.get_doc({ + 'doctype': 'Data Migration Mapping', + 'mapping_name': 'Event to ToDo', + 'remote_objectname': 'Event', + 'remote_primary_key': 'name', + 'local_doctype': 'ToDo', + 'local_primary_key': 'name', + 'mapping_type': 'Pull', + 'condition': '{"subject": "Data migration event"}', + 'fields': [ + { 'remote_fieldname': 'subject', 'local_fieldname': 'description' } + ] + }).insert() + + frappe.get_doc({ + 'doctype': 'Data Migration Plan', + 'plan_name': 'ToDo sync', + 'module': 'Core', + 'mappings': [ + { 'mapping': 'Todo to Event' }, + { 'mapping': 'Event to ToDo' } + ] + }).insert() + + frappe.get_doc({ + 'doctype': 'Data Migration Connector', + 'connector_name': 'Local Connector', + 'connector_type': 'Frappe', + 'hostname': 'http://localhost:8000', + 'username': 'Administrator', + 'password': 'admin' + }).insert() diff --git a/frappe/database.py b/frappe/database.py index 33c3aa8a44..1092636461 100644 --- a/frappe/database.py +++ b/frappe/database.py @@ -14,15 +14,13 @@ import frappe import frappe.defaults import frappe.async import re -import redis import frappe.model.meta from frappe.utils import now, get_datetime, cstr from frappe import _ from six import text_type, binary_type, string_types, integer_types -from frappe.utils.global_search import sync_global_search from frappe.model.utils.link_count import flush_local_link_count from six import iteritems, text_type - +from frappe.utils.background_jobs import execute_job, get_queue class Database: """ @@ -740,20 +738,9 @@ class Database: self.sql("commit") frappe.local.rollback_observers = [] self.flush_realtime_log() - self.enqueue_global_search() + enqueue_jobs_after_commit() flush_local_link_count() - def enqueue_global_search(self): - if frappe.flags.update_global_search: - try: - frappe.enqueue('frappe.utils.global_search.sync_global_search', - now=frappe.flags.in_test or frappe.flags.in_install or frappe.flags.in_migrate, - flags=frappe.flags.update_global_search) - except redis.exceptions.ConnectionError: - sync_global_search() - - frappe.flags.update_global_search = [] - def flush_realtime_log(self): for args in frappe.local.realtime_log: frappe.async.emit_via_redis(*args) @@ -895,3 +882,11 @@ class Database: s = s.replace("%", "%%") return s + +def enqueue_jobs_after_commit(): + if frappe.flags.enqueue_after_commit and len(frappe.flags.enqueue_after_commit) > 0: + for job in frappe.flags.enqueue_after_commit: + q = get_queue(job.get("queue"), async=job.get("async")) + q.enqueue_call(execute_job, timeout=job.get("timeout"), + kwargs=job.get("queue_args")) + frappe.flags.enqueue_after_commit = [] diff --git a/frappe/desk/doctype/event/test_event.py b/frappe/desk/doctype/event/test_event.py index 57300fb560..b1027d501f 100644 --- a/frappe/desk/doctype/event/test_event.py +++ b/frappe/desk/doctype/event/test_event.py @@ -117,13 +117,13 @@ class TestEvent(unittest.TestCase): ev.insert() ev_list = get_events("2014-02-01", "2014-02-01", "Administrator", for_reminder=True) - self.assertTrue(filter(lambda e: e.name==ev.name, ev_list)) + self.assertTrue(list(filter(lambda e: e.name==ev.name, ev_list))) ev_list1 = get_events("2015-01-20", "2015-01-20", "Administrator", for_reminder=True) - self.assertFalse(filter(lambda e: e.name==ev.name, ev_list1)) + self.assertFalse(list(filter(lambda e: e.name==ev.name, ev_list1))) ev_list2 = get_events("2014-02-20", "2014-02-20", "Administrator", for_reminder=True) - self.assertFalse(filter(lambda e: e.name==ev.name, ev_list2)) + self.assertFalse(list(filter(lambda e: e.name==ev.name, ev_list2))) ev_list3 = get_events("2015-02-01", "2015-02-01", "Administrator", for_reminder=True) - self.assertTrue(filter(lambda e: e.name==ev.name, ev_list3)) \ No newline at end of file + self.assertTrue(list(filter(lambda e: e.name==ev.name, ev_list3))) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 8280f6c162..6e10c670d8 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.desk.form.load import get_docinfo +import frappe.share class DuplicateToDoError(frappe.ValidationError): pass @@ -62,6 +63,13 @@ def add(args=None): if frappe.get_meta(args['doctype']).get_field("assigned_to"): frappe.db.set_value(args['doctype'], args['name'], "assigned_to", args['assign_to']) + doc = frappe.get_doc(args['doctype'], args['name']) + + # if assignee does not have permissions, share + if not frappe.has_permission(doc=doc, user=args['assign_to']): + frappe.share.add(doc.doctype, doc.name, args['assign_to']) + frappe.msgprint(_('Shared with user {0} with read access').format(args['assign_to']), alert=True) + # notify notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',\ description=args.get("description"), notify=args.get('notify')) diff --git a/frappe/desk/page/chat/chat_row.html b/frappe/desk/page/chat/chat_row.html index 64c90f7ac2..3c90e7af98 100644 --- a/frappe/desk/page/chat/chat_row.html +++ b/frappe/desk/page/chat/chat_row.html @@ -29,7 +29,7 @@ {% if (data.owner==user) { %}
Delete + onclick="frappe.pages.chat.chat.delete(this)">Delete
{% } %}
diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index 54be2ca009..bc4ce4f043 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -70,7 +70,12 @@ frappe.pages['setup-wizard'].on_page_show = function(wrapper) { frappe.setup.on("before_load", function() { // load slides - frappe.setup.slides_settings.map(frappe.setup.add_slide); + frappe.setup.slides_settings.forEach((s) => { + if(!(s.name==='user' && frappe.boot.developer_mode)) { + // if not user slide with developer mode + frappe.setup.add_slide(s); + } + }); }); frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { @@ -232,6 +237,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { this.working_state_message = this.get_message( __("Setting Up"), __("Sit tight while your system is being setup. This may take a few moments."), + 'orange', true ).appendTo(this.parent); @@ -239,7 +245,8 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { this.current_slide = null; this.completed_state_message = this.get_message( __("Setup Complete"), - __("You're all set!") + __("You're all set!"), + 'green' ); } @@ -501,19 +508,22 @@ frappe.setup.utils = { bind_language_events: function(slide) { slide.get_input("language").unbind("change").on("change", function() { - var lang = $(this).val() || "English"; - frappe._messages = {}; - frappe.call({ - method: "frappe.desk.page.setup_wizard.setup_wizard.load_messages", - freeze: true, - args: { - language: lang - }, - callback: function(r) { - frappe.setup._from_load_messages = true; - frappe.wizard.refresh_slides(); - } - }); + clearTimeout (slide.language_call_timeout); + slide.language_call_timeout = setTimeout (() => { + var lang = $(this).val() || "English"; + frappe._messages = {}; + frappe.call({ + method: "frappe.desk.page.setup_wizard.setup_wizard.load_messages", + freeze: true, + args: { + language: lang + }, + callback: function(r) { + frappe.setup._from_load_messages = true; + frappe.wizard.refresh_slides(); + } + }); + }, 500); }); }, diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index db9f29fc80..9f7d194d22 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -82,7 +82,7 @@ def update_system_settings(args): system_settings.save() def update_user_name(args): - first_name, last_name = args.get('full_name'), '' + first_name, last_name = args.get('full_name', ''), '' if ' ' in first_name: first_name, last_name = first_name.split(' ', 1) @@ -106,7 +106,7 @@ def update_user_name(args): frappe.flags.mute_emails = _mute_emails update_password(args.get("email"), args.get("password")) - else: + elif first_name: args.update({ "name": frappe.session.user, "first_name": first_name, @@ -123,7 +123,8 @@ def update_user_name(args): fileurl = save_file(filename, content, "User", args.get("name"), decode=True).file_url frappe.db.set_value("User", args.get("name"), "user_image", fileurl) - add_all_roles_to(args.get("name")) + if args.get('name'): + add_all_roles_to(args.get("name")) def process_args(args): if not args: diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 09afea8b3d..8b6f32536c 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -352,7 +352,7 @@ def get_filters_cond(doctype, filters, conditions, ignore_permissions=None, with for f in filters: if isinstance(f[1], string_types) and f[1][0] == '!': flt.append([doctype, f[0], '!=', f[1][1:]]) - elif isinstance(f[1], list) and \ + elif isinstance(f[1], (list, tuple)) and \ f[1][0] in (">", "<", ">=", "<=", "like", "not like", "in", "not in", "between"): flt.append([doctype, f[0], f[1][0], f[1][1]]) diff --git a/frappe/desk/treeview.py b/frappe/desk/treeview.py index 24cb6465d2..c02f66952b 100644 --- a/frappe/desk/treeview.py +++ b/frappe/desk/treeview.py @@ -59,7 +59,7 @@ def make_tree_args(**kwarg): doctype = kwarg['doctype'] parent_field = 'parent_' + doctype.lower().replace(' ', '_') - name_field = doctype.lower().replace(' ', '_') + '_name' + name_field = kwarg.get('name_field', doctype.lower().replace(' ', '_') + '_name') kwarg.update({ name_field: kwarg[name_field], diff --git a/frappe/docs/assets/img/data-migration/add-connector-type.png b/frappe/docs/assets/img/data-migration/add-connector-type.png new file mode 100644 index 0000000000..f24dd79bd9 Binary files /dev/null and b/frappe/docs/assets/img/data-migration/add-connector-type.png differ diff --git a/frappe/docs/assets/img/data-migration/atlas-connection-py.png b/frappe/docs/assets/img/data-migration/atlas-connection-py.png new file mode 100644 index 0000000000..3c7e191aff Binary files /dev/null and b/frappe/docs/assets/img/data-migration/atlas-connection-py.png differ diff --git a/frappe/docs/assets/img/data-migration/atlas-connector.png b/frappe/docs/assets/img/data-migration/atlas-connector.png new file mode 100644 index 0000000000..74e9c03373 Binary files /dev/null and b/frappe/docs/assets/img/data-migration/atlas-connector.png differ diff --git a/frappe/docs/assets/img/data-migration/atlas-sync-plan.png b/frappe/docs/assets/img/data-migration/atlas-sync-plan.png new file mode 100644 index 0000000000..104b7bdb85 Binary files /dev/null and b/frappe/docs/assets/img/data-migration/atlas-sync-plan.png differ diff --git a/frappe/docs/assets/img/data-migration/data-migration-run.png b/frappe/docs/assets/img/data-migration/data-migration-run.png new file mode 100644 index 0000000000..584dd930d4 Binary files /dev/null and b/frappe/docs/assets/img/data-migration/data-migration-run.png differ diff --git a/frappe/docs/assets/img/data-migration/edit-connector-py.png b/frappe/docs/assets/img/data-migration/edit-connector-py.png new file mode 100644 index 0000000000..e8d6cbe024 Binary files /dev/null and b/frappe/docs/assets/img/data-migration/edit-connector-py.png differ diff --git a/frappe/docs/assets/img/data-migration/mapping-init-py.png b/frappe/docs/assets/img/data-migration/mapping-init-py.png new file mode 100644 index 0000000000..1cbe941fb9 Binary files /dev/null and b/frappe/docs/assets/img/data-migration/mapping-init-py.png differ diff --git a/frappe/docs/assets/img/data-migration/mapping-pre-and-post-process.png b/frappe/docs/assets/img/data-migration/mapping-pre-and-post-process.png new file mode 100644 index 0000000000..b465bf3107 Binary files /dev/null and b/frappe/docs/assets/img/data-migration/mapping-pre-and-post-process.png differ diff --git a/frappe/docs/assets/img/data-migration/new-connector.png b/frappe/docs/assets/img/data-migration/new-connector.png new file mode 100644 index 0000000000..a0b6d473ab Binary files /dev/null and b/frappe/docs/assets/img/data-migration/new-connector.png differ diff --git a/frappe/docs/assets/img/data-migration/new-data-migration-mapping-fields.png b/frappe/docs/assets/img/data-migration/new-data-migration-mapping-fields.png new file mode 100644 index 0000000000..5eaa5a1b3b Binary files /dev/null and b/frappe/docs/assets/img/data-migration/new-data-migration-mapping-fields.png differ diff --git a/frappe/docs/assets/img/data-migration/new-data-migration-mapping.png b/frappe/docs/assets/img/data-migration/new-data-migration-mapping.png new file mode 100644 index 0000000000..783bac8a50 Binary files /dev/null and b/frappe/docs/assets/img/data-migration/new-data-migration-mapping.png differ diff --git a/frappe/docs/assets/img/data-migration/new-data-migration-plan.png b/frappe/docs/assets/img/data-migration/new-data-migration-plan.png new file mode 100644 index 0000000000..9a14dc9c91 Binary files /dev/null and b/frappe/docs/assets/img/data-migration/new-data-migration-plan.png differ diff --git a/frappe/docs/user/en/guides/data/index.txt b/frappe/docs/user/en/guides/data/index.txt index 4794a002bc..620bafa4e3 100755 --- a/frappe/docs/user/en/guides/data/index.txt +++ b/frappe/docs/user/en/guides/data/index.txt @@ -1 +1,2 @@ import-large-csv-file +using-data-migration-tool diff --git a/frappe/docs/user/en/guides/data/using-data-migration-tool.md b/frappe/docs/user/en/guides/data/using-data-migration-tool.md new file mode 100644 index 0000000000..a7a57cb8c5 --- /dev/null +++ b/frappe/docs/user/en/guides/data/using-data-migration-tool.md @@ -0,0 +1,99 @@ +# Using the Data Migration Tool + +> Data Migration Tool was introduced in Frappé Framework version 9. + +The Data Migration Tool was built to abstract all the syncing of data between a remote source and a DocType. This is a middleware layer between your Frappé based website and a remote data source. + +To understand this tool, let's make a connector to push ERPNext Items to an imaginary service called Atlas. + +### Data Migration Plan +A Data Migration Plan encapsulates a set of mappings. + +Let's make a new *Data Migration Plan*. Set the plan name as 'Atlas Sync'. We also need to add mappings in the mappings child table. + +New Data Migration Plan + + +### Data Migration Mapping +A Data Migration Mapping is a set of rules that specify field-to-field mapping. + +Make a new *Data Migration Mapping*. Call it 'Item to Atlas Item'. + +To define a mapping, we need to put in some values that define the structure of local and remote data. + +1. Remote Objectname: A name that identifies the remote object e.g Atlas Item +1. Remote primary key: This is the name of the primary key for Atlas Item e.g id +1. Local DocType: The DocType which will be used for syncing e.g Item +1. Mapping Type: A Mapping can be of type 'Push' or 'Pull', depending on whether the data is to be mapped remotely or locally. It can also be 'Sync', which will perform both push and pull operations in a single cycle. +1. Page Length: This defines the batch size of the sync. + +New Data Migration Mapping + +#### Specifying field mappings: + +The most basic form of a field mapping would be to specify fieldnames of the remote and local object. However, if the mapping is one-way (push or pull), the source field name can also take literal values in quotes (for e.g `"GadgetTech"`) and eval statements (for e.g `"eval:frappe.db.get_value('Company', 'Gadget Tech', 'default_currency')"`). For example, in the case of a push mapping, the local fieldname can be set to a string in quotes or an `eval` expression, instead of a field name from the local doctype. (This is not possible with a sync mapping, where both local and remote fieldnames serve as a target destination at a some point, and thus cannot be a literal value). + +Let's add the field mappings and save: + +Add fields in Data Migration Mapping + +We can now add the 'Item to Atlas Item' mapping to our Data Migration Plan and save it. + +Save Atlas Sync Plan + +#### Additional layer of control with pre and post process: + +Migrating data frequently involves more steps in addition to one-to-one mapping. For a Data Migration Mapping that is added to a Plan, a mapping module is generated in the module specified in that plan. + +In our case, an `item_to_atlas_item` module is created under the `data_migration_mapping` directory in `Integrations` (module for the 'Atlas Sync' plan). + +Mapping __init__.py + +You can implement the `pre_process` (receives the source doc) and `post_process` (receives both source and target docs, as well as any additional arguments) methods, to extend the mapping process. Here's what some operations could look like: + +Pre and Post Process + +### Data Migration Connector +Now, to connect to the remote source, we need to create a *Data Migration Connector*. + +New Data Migration Connector + +We only have two connector types right now, let's add another Connector Type in the Data Migration Connector DocType. + +Add Connector Type in Data Migration Connector + +Now, let's create a new Data Migration Connector. + +Atlas Connector + +As you can see we chose the Connector Type as Atlas. We also added the hostname, username and password for our Atlas instance so that we can authenticate. + +Now, we need to write the code for our connector so that we can actually push data. + +Create a new file called `atlas_connection.py` in `frappe/data_migration/doctype/data_migration_connector/connectors/` directory. Other connectors also live here. + +We just have to implement the `insert`, `update` and `delete` methods for our atlas connector. We also need to write the code to connect to our Atlas instance in the `__init__` method. Just see `frappe_connection.py` for reference. + +Atlas Connection file + +After creating the Atlas Connector, we also need to import it into `data_migration_connector.py` + +Edit Connector file + +### Data Migration Run +Now that we have our connector, the last thing to do is to create a new *Data Migration Run*. + +A Data Migration Run takes a Data Migration Plan and Data Migration Connector and execute the plan according to our configuration. It takes care of queueing, batching, delta updates and more. + +Data Migration Run + +Just click Run. It will now push our Items to the remote Atlas instance and you can see the progress which updates in realtime. + +After a run is executed successfully, you cannot run it again. You will have to create another run and execute it. + +Data Migration Run will try to be as efficient as possible, so the next time you execute it, it will only push those items which were changed or failed in the last run. + + +> Note: Data Migration Tool is still in beta. If you find any issues please report them [here](https://github.com/frappe/erpnext/issues) + + \ No newline at end of file diff --git a/frappe/email/doctype/email_account/test_email_account.py b/frappe/email/doctype/email_account/test_email_account.py index 549d721e64..d694abb3d3 100644 --- a/frappe/email/doctype/email_account/test_email_account.py +++ b/frappe/email/doctype/email_account/test_email_account.py @@ -111,7 +111,7 @@ class TestEmailAccount(unittest.TestCase): frappe.sendmail(sender="test_sender@example.com", recipients="test_recipient@example.com", content="test mail 001", subject="test-mail-001", delayed=False) - sent_mail = email.message_from_string(frappe.flags.sent_mail) + sent_mail = email.message_from_string(frappe.flags.sent_mail.decode()) self.assertTrue("test-mail-001" in sent_mail.get("Subject")) def test_print_format(self): diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index 638905c391..b3a947312f 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -10,6 +10,7 @@ from frappe.utils import (get_url, scrub_urls, strip, expand_relative_urls, cint import email.utils from six import iteritems, text_type, string_types from email.mime.multipart import MIMEMultipart +from email.header import Header def get_email(recipients, sender='', msg='', subject='[No Subject]', @@ -183,7 +184,7 @@ class EMail: if cint(self.email_account.always_use_account_email_id_as_sender): self.set_header('X-Original-From', self.sender) sender_name, sender_email = parse_addr(self.sender) - self.sender = email.utils.formataddr((sender_name or self.email_account.name, self.email_account.email_id)) + self.sender = email.utils.formataddr((str(Header(sender_name or self.email_account.name, 'utf-8')), self.email_account.email_id)) def set_message_id(self, message_id, is_notification=False): if message_id: @@ -321,9 +322,9 @@ def add_attachment(fname, fcontent, content_type=None, # Set the filename parameter if fname: attachment_type = 'inline' if inline else 'attachment' - part.add_header(b'Content-Disposition', attachment_type, filename=text_type(fname)) + part.add_header('Content-Disposition', attachment_type, filename=text_type(fname)) if content_id: - part.add_header(b'Content-ID', '<{0}>'.format(content_id)) + part.add_header('Content-ID', '<{0}>'.format(content_id)) parent.attach(part) @@ -414,7 +415,7 @@ def get_filecontent_from_path(path): full_path = path if os.path.exists(full_path): - with open(full_path) as f: + with open(full_path, 'rb') as f: filecontent = f.read() return filecontent diff --git a/frappe/email/test_email_body.py b/frappe/email/test_email_body.py index f68e2684af..bec6be5e5e 100644 --- a/frappe/email/test_email_body.py +++ b/frappe/email/test_email_body.py @@ -21,9 +21,9 @@ This is the text version of this email ''' img_path = os.path.abspath('assets/frappe/images/favicon.png') - with open(img_path) as f: + with open(img_path, 'rb') as f: img_content = f.read() - img_base64 = base64.b64encode(img_content) + img_base64 = base64.b64encode(img_content).decode() # email body keeps 76 characters on one line self.img_base64 = fixed_column_width(img_base64, 76) diff --git a/frappe/frappeclient.py b/frappe/frappeclient.py index d619e0d6ee..e323ffb7c6 100644 --- a/frappe/frappeclient.py +++ b/frappe/frappeclient.py @@ -16,6 +16,7 @@ class FrappeException(Exception): class FrappeClient(object): def __init__(self, url, username, password, verify=True): + self.headers = dict(Accept='application/json') self.verify = verify self.session = requests.session() self.url = url @@ -33,7 +34,7 @@ class FrappeClient(object): 'cmd': 'login', 'usr': username, 'pwd': password - }, verify=self.verify) + }, verify=self.verify, headers=self.headers) if r.status_code==200 and r.json().get('message') == "Logged In": return r.json() @@ -45,7 +46,7 @@ class FrappeClient(object): '''Logout session''' self.session.get(self.url, params={ 'cmd': 'logout', - }, verify=self.verify) + }, verify=self.verify, headers=self.headers) def get_list(self, doctype, fields='"*"', filters=None, limit_start=0, limit_page_length=0): """Returns list of records of a particular type""" @@ -59,7 +60,7 @@ class FrappeClient(object): if limit_page_length: params["limit_start"] = limit_start params["limit_page_length"] = limit_page_length - res = self.session.get(self.url + "/api/resource/" + doctype, params=params, verify=self.verify) + res = self.session.get(self.url + "/api/resource/" + doctype, params=params, verify=self.verify, headers=self.headers) return self.post_process(res) def insert(self, doc): @@ -67,7 +68,7 @@ class FrappeClient(object): :param doc: A dict or Document object to be inserted remotely''' res = self.session.post(self.url + "/api/resource/" + doc.get("doctype"), - data={"data":frappe.as_json(doc)}, verify=self.verify) + data={"data":frappe.as_json(doc)}, verify=self.verify, headers=self.headers) return self.post_process(res) def insert_many(self, docs): @@ -84,7 +85,7 @@ class FrappeClient(object): :param doc: dict or Document object to be updated remotely. `name` is mandatory for this''' url = self.url + "/api/resource/" + doc.get("doctype") + "/" + doc.get("name") - res = self.session.put(url, data={"data":frappe.as_json(doc)}, verify=self.verify) + res = self.session.put(url, data={"data":frappe.as_json(doc)}, verify=self.verify, headers=self.headers) return self.post_process(res) def bulk_update(self, docs): @@ -169,7 +170,7 @@ class FrappeClient(object): params["fields"] = json.dumps(fields) res = self.session.get(self.url + "/api/resource/" + doctype + "/" + name, - params=params, verify=self.verify) + params=params, verify=self.verify, headers=self.headers) return self.post_process(res) @@ -251,21 +252,21 @@ class FrappeClient(object): def get_api(self, method, params={}): res = self.session.get(self.url + "/api/method/" + method + "/", - params=params, verify=self.verify) + params=params, verify=self.verify, headers=self.headers) return self.post_process(res) def post_api(self, method, params={}): res = self.session.post(self.url + "/api/method/" + method + "/", - params=params, verify=self.verify) + params=params, verify=self.verify, headers=self.headers) return self.post_process(res) def get_request(self, params): - res = self.session.get(self.url, params=self.preprocess(params), verify=self.verify) + res = self.session.get(self.url, params=self.preprocess(params), verify=self.verify, headers=self.headers) res = self.post_process(res) return res def post_request(self, data): - res = self.session.post(self.url, data=self.preprocess(data), verify=self.verify) + res = self.session.post(self.url, data=self.preprocess(data), verify=self.verify, headers=self.headers) res = self.post_process(res) return res diff --git a/frappe/hooks.py b/frappe/hooks.py index 707b2fac37..503e60f811 100755 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -30,7 +30,6 @@ app_include_js = [ "assets/js/form.min.js", "assets/js/control.min.js", "assets/js/report.min.js", - "assets/js/d3.min.js", "assets/frappe/js/frappe/toolbar.js" ] app_include_css = [ @@ -157,15 +156,19 @@ scheduler_events = { "frappe.core.doctype.authentication_log.authentication_log.clear_authentication_logs" ], "daily_long": [ - "frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backups_daily" + "frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backups_daily", + "frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_daily" ], "weekly_long": [ - "frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backups_weekly" + "frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backups_weekly", + "frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_weekly" ], "monthly": [ "frappe.email.doctype.auto_email_report.auto_email_report.send_monthly" + ], + "monthly_long": [ + "frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_monthly" ] - } get_translated_dict = { diff --git a/frappe/integrations/doctype/oauth_client/oauth_client.json b/frappe/integrations/doctype/oauth_client/oauth_client.json index 1bd83a8e51..47ede6e280 100644 --- a/frappe/integrations/doctype/oauth_client/oauth_client.json +++ b/frappe/integrations/doctype/oauth_client/oauth_client.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, "autoname": "", @@ -13,6 +14,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -24,6 +26,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "App Client ID", @@ -41,6 +44,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -51,6 +55,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "App Name", @@ -69,6 +74,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -79,6 +85,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "User", @@ -98,6 +105,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -108,6 +116,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "length": 0, @@ -125,6 +134,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -135,6 +145,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "App Client Secret", @@ -153,6 +164,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -164,6 +176,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Skip Authorization", @@ -182,6 +195,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -193,6 +207,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "", @@ -211,6 +226,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -223,6 +239,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Scopes", @@ -240,6 +257,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -250,6 +268,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "length": 0, @@ -267,6 +286,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -278,6 +298,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Redirect URIs", @@ -295,6 +316,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -305,6 +327,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Default Redirect URI", @@ -323,6 +346,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -334,6 +358,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": " Advanced Settings", @@ -352,6 +377,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -362,12 +388,13 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Grant Type", "length": 0, "no_copy": 0, - "options": "Authorization Code\nImplicit\nResource Owner Password Credentials\nClient Credentials", + "options": "Authorization Code\nImplicit", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -380,6 +407,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -390,6 +418,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "length": 0, @@ -407,6 +436,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -418,6 +448,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Response Type", @@ -436,17 +467,17 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 0, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-03-08 14:40:03.031779", + "modified": "2017-10-05 21:07:39.476360", "modified_by": "Administrator", "module": "Integrations", "name": "OAuth Client", @@ -463,7 +494,6 @@ "export": 1, "if_owner": 0, "import": 0, - "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -478,6 +508,7 @@ "quick_entry": 0, "read_only": 0, "read_only_onload": 0, + "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", "title_field": "app_name", diff --git a/frappe/integrations/doctype/oauth_client/oauth_client.py b/frappe/integrations/doctype/oauth_client/oauth_client.py index ebf2f64547..2663623bfa 100644 --- a/frappe/integrations/doctype/oauth_client/oauth_client.py +++ b/frappe/integrations/doctype/oauth_client/oauth_client.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document class OAuthClient(Document): @@ -11,3 +12,8 @@ class OAuthClient(Document): self.client_id = self.name if not self.client_secret: self.client_secret = frappe.generate_hash(length=10) + self.validate_grant_and_response() + def validate_grant_and_response(self): + if self.grant_type == "Authorization Code" and self.response_type != "Code" or \ + self.grant_type == "Implicit" and self.response_type != "Token": + frappe.throw(_("Combination of Grant Type ({0}) and Response Type ({1}) not allowed".format(self.grant_type, self.response_type))) diff --git a/frappe/integrations/doctype/s3_backup_settings/__init__.py b/frappe/integrations/doctype/s3_backup_settings/__init__.py new file mode 100755 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.js b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.js new file mode 100755 index 0000000000..1a1b8a7c67 --- /dev/null +++ b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.js @@ -0,0 +1,26 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('S3 Backup Settings', { + refresh: function(frm) { + frm.clear_custom_buttons(); + frm.events.take_backup(frm); + }, + + take_backup: function(frm) { + if (frm.doc.access_key_id && frm.doc.secret_access_key) { + frm.add_custom_button(__("Take Backup Now"), function(){ + frm.dashboard.set_headline_alert("S3 Backup Started!"); + frappe.call({ + method: "frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_s3", + callback: function(r) { + if(!r.exc) { + frappe.msgprint(__("S3 Backup complete!")); + frm.dashboard.clear_headline(); + } + } + }); + }).addClass("btn-primary"); + } + } +}); diff --git a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.json b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.json new file mode 100755 index 0000000000..0cdc8e1dd6 --- /dev/null +++ b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.json @@ -0,0 +1,273 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-09-04 20:57:20.129205", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "enabled", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Enable Automatic Backup", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "notify_email", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Send Notifications To", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "frequency", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Backup Frequency", + "length": 0, + "no_copy": 0, + "options": "Daily\nWeekly\nMonthly\nNone", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "access_key_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Access Key ID", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "secret_access_key", + "fieldtype": "Password", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Secret Access Key", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "bucket", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Bucket", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "backup_limit", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Backup Limit", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 1, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-06 18:27:09.022674", + "modified_by": "Administrator", + "module": "Integrations", + "name": "S3 Backup Settings", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py new file mode 100755 index 0000000000..c557365e6b --- /dev/null +++ b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import os +import os.path +import frappe +import boto3 +from frappe import _ +from frappe.model.document import Document +from frappe.utils import cint, split_emails +from frappe.utils.background_jobs import enqueue +from botocore.exceptions import ClientError + +class S3BackupSettings(Document): + + def validate(self): + conn = boto3.client( + 's3', + aws_access_key_id=self.access_key_id, + aws_secret_access_key=self.get_password('secret_access_key'), + ) + + bucket_lower = str(self.bucket).lower() + + try: + conn.list_buckets() + + except ClientError: + frappe.throw(_("Invalid Access Key ID or Secret Access Key.")) + + try: + conn.create_bucket(Bucket=bucket_lower) + except ClientError: + frappe.throw(_("Unable to create bucket: {0}. Change it to a more unique name.").format(bucket_lower)) + + +@frappe.whitelist() +def take_backup(): + "Enqueue longjob for taking backup to s3" + enqueue("frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_s3", queue='long', timeout=1500) + frappe.msgprint(_("Queued for backup. It may take a few minutes to an hour.")) + + +def take_backups_daily(): + take_backups_if("Daily") + + +def take_backups_weekly(): + take_backups_if("Weekly") + + +def take_backups_monthly(): + take_backups_if("Monthly") + + +def take_backups_if(freq): + if cint(frappe.db.get_value("S3 Backup Settings", None, "enabled")): + if frappe.db.get_value("S3 Backup Settings", None, "frequency") == freq: + take_backups_s3() + + +@frappe.whitelist() +def take_backups_s3(): + try: + backup_to_s3() + send_email(True, "S3 Backup Settings") + except Exception: + error_message = frappe.get_traceback() + frappe.errprint(error_message) + send_email(False, "S3 Backup Settings", error_message) + + +def send_email(success, service_name, error_status=None): + if success: + subject = "Backup Upload Successful" + message = """

Backup Uploaded Successfully!

Hi there, this is just to inform you + that your backup was successfully uploaded to your Amazon S3 bucket. So relax!

""" + + else: + subject = "[Warning] Backup Upload Failed" + message = """

Backup Upload Failed!

Oops, your automated backup to Amazon S3 failed. +

Error message: %s

Please contact your system manager + for more information.

""" % error_status + + if not frappe.db: + frappe.connect() + + if frappe.db.get_value("S3 Backup Settings", None, "notification_email"): + recipients = split_emails(frappe.db.get_value("S3 Backup Settings", None, "notification_email")) + frappe.sendmail(recipients=recipients, subject=subject, message=message) + + +def backup_to_s3(): + from frappe.utils.backups import new_backup + from frappe.utils import get_backups_path + + doc = frappe.get_single("S3 Backup Settings") + bucket = doc.bucket + + conn = boto3.client( + 's3', + aws_access_key_id=doc.access_key_id, + aws_secret_access_key=doc.get_password('secret_access_key'), + ) + + backup = new_backup(ignore_files=False, backup_path_db=None, + backup_path_files=None, backup_path_private_files=None, force=True) + db_filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_db)) + files_filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_files)) + private_files = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_private_files)) + folder = os.path.basename(db_filename)[:15] + '/' + # for adding datetime to folder name + + upload_file_to_s3(db_filename, folder, conn, bucket) + upload_file_to_s3(private_files, folder, conn, bucket) + upload_file_to_s3(files_filename, folder, conn, bucket) + delete_old_backups(doc.backup_limit, bucket) + +def upload_file_to_s3(filename, folder, conn, bucket): + + destpath = os.path.join(folder, os.path.basename(filename)) + try: + print "Uploading file:", filename + conn.upload_file(filename, bucket, destpath) + + except Exception as e: + print "Error uploading: %s" % (e) + + +def delete_old_backups(limit, bucket): + all_backups = list() + doc = frappe.get_single("S3 Backup Settings") + backup_limit = int(limit) + + s3 = boto3.resource( + 's3', + aws_access_key_id=doc.access_key_id, + aws_secret_access_key=doc.get_password('secret_access_key'), + ) + bucket = s3.Bucket(bucket) + objects = bucket.meta.client.list_objects_v2(Bucket=bucket.name, Delimiter='/') + for obj in objects.get('CommonPrefixes'): + all_backups.append(obj.get('Prefix')) + + oldest_backup = sorted(all_backups)[0] + + if len(all_backups) > backup_limit: + print "Deleting Backup: {0}".format(oldest_backup) + for obj in bucket.objects.filter(Prefix=oldest_backup): + # delete all keys that are inside the oldest_backup + s3.Object(bucket.name, obj.key).delete() diff --git a/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.js b/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.js new file mode 100755 index 0000000000..27e36661f0 --- /dev/null +++ b/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: S3 Backup Settings", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new S3 Backup Settings + () => frappe.tests.make('S3 Backup Settings', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.py b/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.py new file mode 100755 index 0000000000..04d90f9b44 --- /dev/null +++ b/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +import unittest + +class TestS3BackupSettings(unittest.TestCase): + pass diff --git a/frappe/integrations/doctype/webhook/__init__.py b/frappe/integrations/doctype/webhook/__init__.py index 3c8bf1e2af..b72acc0578 100644 --- a/frappe/integrations/doctype/webhook/__init__.py +++ b/frappe/integrations/doctype/webhook/__init__.py @@ -37,7 +37,8 @@ def run_webhooks(doc, method): def _webhook_request(webhook): if not webhook.name in frappe.flags.webhooks_executed.get(doc.name, []): - frappe.enqueue("frappe.integrations.doctype.webhook.webhook.enqueue_webhook", doc=doc, webhook=webhook) + frappe.enqueue("frappe.integrations.doctype.webhook.webhook.enqueue_webhook", + enqueue_after_commit=True, doc=doc, webhook=webhook) # keep list of webhooks executed for this doc in this request # so that we don't run the same webhook for the same document multiple times diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 4a5568b238..b3d0bdc277 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -362,39 +362,6 @@ class BaseDocument(object): # this is used to preserve traceback raise frappe.UniqueValidationError(self.doctype, self.name, e) - def db_set(self, fieldname, value=None, update_modified=True): - '''Set a value in the document object, update the timestamp and update the database. - - WARNING: This method does not trigger controller validations and should - be used very carefully. - - :param fieldname: fieldname of the property to be updated, or a {"field":"value"} dictionary - :param value: value of the property to be updated - :param update_modified: default True. updates the `modified` and `modified_by` properties - ''' - if isinstance(fieldname, dict): - self.update(fieldname) - else: - self.set(fieldname, value) - - if update_modified and (self.doctype, self.name) not in frappe.flags.currently_saving: - # don't update modified timestamp if called from post save methods - # like on_update or on_submit - self.set("modified", now()) - self.set("modified_by", frappe.session.user) - - # to trigger email alert on value change - self.run_method('before_change') - - frappe.db.set_value(self.doctype, self.name, fieldname, value, - self.modified, self.modified_by, update_modified=update_modified) - - self.run_method('on_change') - - def db_get(self, fieldname): - '''get database vale for this fieldname''' - return frappe.db.get_value(self.doctype, self.name, fieldname) - def update_modified(self): '''Update modified timestamp''' self.set("modified", now()) diff --git a/frappe/model/db_schema.py b/frappe/model/db_schema.py index e47c678e4c..fb3de07332 100644 --- a/frappe/model/db_schema.py +++ b/frappe/model/db_schema.py @@ -44,6 +44,7 @@ type_map = { ,'Attach Image':('text', '') ,'Signature': ('longtext', '') ,'Color': ('varchar', varchar_len) + ,'Barcode': ('longtext', '') } default_columns = ['name', 'creation', 'modified', 'modified_by', 'owner', diff --git a/frappe/model/document.py b/frappe/model/document.py index 56a14cff8c..6d11a7607d 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -867,6 +867,46 @@ class Document(BaseDocument): not self.meta.get("istable"): frappe.publish_realtime("list_update", {"doctype": self.doctype}, after_commit=True) + def db_set(self, fieldname, value=None, update_modified=True, notify=False, commit=False): + '''Set a value in the document object, update the timestamp and update the database. + + WARNING: This method does not trigger controller validations and should + be used very carefully. + + :param fieldname: fieldname of the property to be updated, or a {"field":"value"} dictionary + :param value: value of the property to be updated + :param update_modified: default True. updates the `modified` and `modified_by` properties + :param notify: default False. run doc.notify_updated() to send updates via socketio + :param commit: default False. run frappe.db.commit() + ''' + if isinstance(fieldname, dict): + self.update(fieldname) + else: + self.set(fieldname, value) + + if update_modified and (self.doctype, self.name) not in frappe.flags.currently_saving: + # don't update modified timestamp if called from post save methods + # like on_update or on_submit + self.set("modified", now()) + self.set("modified_by", frappe.session.user) + + # to trigger email alert on value change + self.run_method('before_change') + + frappe.db.set_value(self.doctype, self.name, fieldname, value, + self.modified, self.modified_by, update_modified=update_modified) + + self.run_method('on_change') + + if notify: + self.notify_update() + + if commit: + frappe.db.commit() + + def db_get(self, fieldname): + '''get database vale for this fieldname''' + return frappe.db.get_value(self.doctype, self.name, fieldname) def check_no_back_links_exist(self): """Check if document links to any active document before Cancel.""" diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 74cce521a5..69182cdeff 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -93,6 +93,9 @@ class Meta(Document): return self.get("fields", {"fieldtype": "Select", "options":["not in", ["[Select]", "Loading..."]]}) + def get_image_fields(self): + return self.get("fields", {"fieldtype": "Attach Image"}) + def get_table_fields(self): if not hasattr(self, "_table_fields"): if self.name!="DocType": @@ -215,7 +218,7 @@ class Meta(Document): title_field = getattr(self, 'title_field', None) if not title_field and self.has_field('title'): title_field = 'title' - else: + if not title_field: title_field = 'name' return title_field diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 9c80a946a1..de2bca903c 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -56,8 +56,10 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=False): """walk and sync all doctypes and pages""" + # load in sequence - warning for devs document_types = ['doctype', 'page', 'report', 'print_format', - 'website_theme', 'web_form', 'email_alert', 'print_style'] + 'website_theme', 'web_form', 'email_alert', 'print_style', + 'data_migration_mapping', 'data_migration_plan'] for doctype in document_types: doctype_path = os.path.join(start_path, doctype) if os.path.exists(doctype_path): diff --git a/frappe/modules.txt b/frappe/modules.txt index 0d2e91b35f..a4ceff3d39 100644 --- a/frappe/modules.txt +++ b/frappe/modules.txt @@ -8,3 +8,4 @@ Desk Integrations Printing Contacts +Data Migration \ No newline at end of file diff --git a/frappe/modules/export_file.py b/frappe/modules/export_file.py index 2b7a6cab3b..275db10b13 100644 --- a/frappe/modules/export_file.py +++ b/frappe/modules/export_file.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, os, json import frappe.model -from frappe.modules import scrub, get_module_path, lower_case_files_for, scrub_dt_dn +from frappe.modules import scrub, get_module_path, scrub_dt_dn def export_doc(doc): export_to_files([[doc.doctype, doc.name]]) @@ -21,7 +21,7 @@ def export_to_files(record_list=None, record_module=None, verbose=0, create_init for record in record_list: write_document_file(frappe.get_doc(record[0], record[1]), record_module, create_init=create_init) -def write_document_file(doc, record_module=None, create_init=None): +def write_document_file(doc, record_module=None, create_init=True): newdoc = doc.as_dict(no_nulls=True) # strip out default fields from children @@ -32,14 +32,12 @@ def write_document_file(doc, record_module=None, create_init=None): del d[fieldname] module = record_module or get_module_name(doc) - if create_init is None: - create_init = doc.doctype in lower_case_files_for # create folder folder = create_folder(module, doc.doctype, doc.name, create_init) # write the data file - fname = (doc.doctype in lower_case_files_for and scrub(doc.name)) or doc.name + fname = scrub(doc.name) with open(os.path.join(folder, fname +".json"),'w+') as txtfile: txtfile.write(frappe.as_json(newdoc)) diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index 9e43a28c6f..499d2bbb56 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -9,11 +9,6 @@ import frappe, os, json import frappe.utils from frappe import _ -lower_case_files_for = ['DocType', 'Page', 'Report', - "Workflow", 'Module Def', 'Desktop Item', 'Workflow State', - 'Workflow Action', 'Print Format', "Website Theme", 'Web Form', - 'Email Alert', 'Print Style'] - def export_module_json(doc, is_standard, module): """Make a folder for the given doc and add its json file (make it a standard object that will be synced)""" @@ -22,7 +17,8 @@ def export_module_json(doc, is_standard, module): from frappe.modules.export_file import export_to_files # json - export_to_files(record_list=[[doc.doctype, doc.name]], record_module=module) + export_to_files(record_list=[[doc.doctype, doc.name]], record_module=module, + create_init=is_standard) path = os.path.join(frappe.get_module_path(module), scrub(doc.doctype), scrub(doc.name), scrub(doc.name)) @@ -134,11 +130,7 @@ def scrub(txt): def scrub_dt_dn(dt, dn): """Returns in lowercase and code friendly names of doctype and name for certain types""" - ndt, ndn = dt, dn - if dt in lower_case_files_for: - ndt, ndn = scrub(dt), scrub(dn) - - return ndt, ndn + return scrub(dt), scrub(dn) def get_module_path(module): """Returns path of the given module""" diff --git a/frappe/oauth.py b/frappe/oauth.py index 246f771f2a..61b4db5034 100644 --- a/frappe/oauth.py +++ b/frappe/oauth.py @@ -206,7 +206,10 @@ class OAuthWebRequestValidator(RequestValidator): otoken = frappe.new_doc("OAuth Bearer Token") otoken.client = request.client['name'] - otoken.user = request.user if request.user else frappe.db.get_value("OAuth Bearer Token", {"refresh_token":request.body.get("refresh_token")}, "user") + try: + otoken.user = request.user if request.user else frappe.db.get_value("OAuth Bearer Token", {"refresh_token":request.body.get("refresh_token")}, "user") + except Exception as e: + otoken.user = frappe.session.user otoken.scopes = get_url_delimiter().join(request.scopes) otoken.access_token = token['access_token'] otoken.refresh_token = token.get('refresh_token') diff --git a/frappe/patches.txt b/frappe/patches.txt index 7c6156ac3c..38f8fd719e 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -7,10 +7,11 @@ frappe.patches.v7_1.rename_scheduler_log_to_error_log frappe.patches.v6_1.rename_file_data frappe.patches.v7_0.re_route #2016-06-27 frappe.patches.v7_2.remove_in_filter -frappe.patches.v8_0.drop_in_dialog -execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2017-03-09 +frappe.patches.v8_0.drop_in_dialog #2017-09-22 +execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2017-09-22 execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2017-03-03 execute:frappe.reload_doc('core', 'doctype', 'docperm') #2017-03-03 +execute:frappe.reload_doc('core', 'doctype', 'module_def') #2017-09-22 frappe.patches.v8_0.drop_is_custom_from_docperm frappe.patches.v8_0.update_records_in_global_search #11-05-2017 frappe.patches.v8_0.update_published_in_global_search diff --git a/frappe/public/build.json b/frappe/public/build.json index 8d10760cf1..1a418248dd 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -51,7 +51,9 @@ "public/js/frappe/form/controls/read_only.js", "public/js/frappe/form/controls/button.js", "public/js/frappe/form/controls/html.js", - "public/js/frappe/form/controls/heading.js" + "public/js/frappe/form/controls/heading.js", + "public/js/frappe/form/controls/autocomplete.js", + "public/js/frappe/form/controls/barcode.js" ], "js/dialog.min.js": [ "public/js/frappe/dom.js", @@ -101,7 +103,6 @@ "public/css/bootstrap.css", "public/css/font-awesome.css", "public/css/octicons/octicons.css", - "public/css/c3.min.css", "public/css/desk.css", "public/css/indicator.css", "public/css/avatar.css", @@ -113,7 +114,7 @@ "public/css/form.css", "public/css/mobile.css", "public/css/kanban.css", - "public/css/graphs.css" + "public/css/charts.css" ], "css/frappe-rtl.css": [ "public/css/bootstrap-rtl.css", @@ -150,6 +151,7 @@ "public/js/frappe/ui/keyboard.js", "public/js/frappe/ui/emoji.js", "public/js/frappe/ui/colors.js", + "public/js/frappe/ui/sidebar.js", "public/js/frappe/request.js", "public/js/frappe/socketio_client.js", @@ -229,16 +231,11 @@ "public/js/frappe/query_string.js", "public/js/frappe/ui/charts.js", - "public/js/frappe/ui/graphs.js", "public/js/frappe/ui/comment.js", "public/js/frappe/misc/rating_icons.html", "public/js/frappe/feedback.js" ], - "js/d3.min.js": [ - "public/js/lib/d3.min.js", - "public/js/lib/c3.min.js" - ], "css/module.min.css": [ "public/css/module.css" ], @@ -269,7 +266,7 @@ "public/js/frappe/form/linked_with.js", "public/js/frappe/form/workflow.js", "public/js/frappe/form/print.js", - "public/js/frappe/form/sidebar.js", + "public/js/frappe/form/form_sidebar.js", "public/js/frappe/form/user_image.js", "public/js/frappe/form/share.js", "public/js/frappe/form/form_viewers.js", diff --git a/frappe/public/css/c3.min.css b/frappe/public/css/c3.min.css deleted file mode 100644 index 1e20d5b116..0000000000 --- a/frappe/public/css/c3.min.css +++ /dev/null @@ -1 +0,0 @@ -.c3 svg{font:10px sans-serif;-webkit-tap-highlight-color:transparent}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:gray;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-title{font:14px sans-serif}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #CCC}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#FFF}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max,.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000} \ No newline at end of file diff --git a/frappe/public/css/graphs.css b/frappe/public/css/charts.css similarity index 77% rename from frappe/public/css/graphs.css rename to frappe/public/css/charts.css index e0f62b3cd9..f5d279568a 100644 --- a/frappe/public/css/graphs.css +++ b/frappe/public/css/charts.css @@ -1,74 +1,74 @@ -/* graphs */ -.graph-container .graph-focus-margin { +/* charts */ +.chart-container .graph-focus-margin { margin: 0px 5%; } -.graph-container .graphics { +.chart-container .graphics { margin-top: 10px; padding-top: 10px; padding-bottom: 10px; position: relative; } -.graph-container .graph-stats-group { +.chart-container .graph-stats-group { display: flex; justify-content: space-around; flex: 1; } -.graph-container .graph-stats-container { +.chart-container .graph-stats-container { display: flex; justify-content: space-around; padding-top: 10px; } -.graph-container .graph-stats-container .stats { +.chart-container .graph-stats-container .stats { padding-bottom: 15px; } -.graph-container .graph-stats-container .stats-title { +.chart-container .graph-stats-container .stats-title { color: #8D99A6; } -.graph-container .graph-stats-container .stats-value { +.chart-container .graph-stats-container .stats-value { font-size: 20px; font-weight: 300; } -.graph-container .graph-stats-container .stats-description { +.chart-container .graph-stats-container .stats-description { font-size: 12px; color: #8D99A6; } -.graph-container .graph-stats-container .graph-data .stats-value { +.chart-container .graph-stats-container .graph-data .stats-value { color: #98d85b; } -.graph-container .axis, -.graph-container .chart-label { +.chart-container .axis, +.chart-container .chart-label { font-size: 10px; - fill: #959ba1; + fill: #555b51; } -.graph-container .axis line, -.graph-container .chart-label line { - stroke: rgba(27, 31, 35, 0.1); +.chart-container .axis line, +.chart-container .chart-label line { + stroke: rgba(27, 31, 35, 0.2); } -.graph-container .percentage-graph .progress { +.chart-container .percentage-graph .progress { margin-bottom: 0px; } -.graph-container .data-points circle { +.chart-container .data-points circle { stroke: #fff; stroke-width: 2; } -.graph-container .data-points path { +.chart-container .data-points path { fill: none; stroke-opacity: 1; stroke-width: 2px; } -.graph-container line.dashed { +.chart-container line.dashed { stroke-dasharray: 5,3; } -.graph-container .tick.x-axis-label { +.chart-container .tick.x-axis-label { display: block; } -.graph-container .tick .specific-value { +.chart-container .tick .specific-value { text-anchor: start; } -.graph-container .tick .y-value-text { +.chart-container .tick .y-value-text { text-anchor: end; } -.graph-container .tick .x-value-text { +.chart-container .tick .x-value-text { text-anchor: middle; } .graph-svg-tip { @@ -138,6 +138,9 @@ .stroke.light-green { stroke: #98d85b; } +.stroke.lightgreen { + stroke: #98d85b; +} .stroke.green { stroke: #28a745; } @@ -174,6 +177,9 @@ .fill.light-green { fill: #98d85b; } +.fill.lightgreen { + fill: #98d85b; +} .fill.green { fill: #28a745; } @@ -210,6 +216,9 @@ .background.light-green { background: #98d85b; } +.background.lightgreen { + background: #98d85b; +} .background.green { background: #28a745; } @@ -246,6 +255,9 @@ .border-top.light-green { border-top: 3px solid #98d85b; } +.border-top.lightgreen { + border-top: 3px solid #98d85b; +} .border-top.green { border-top: 3px solid #28a745; } diff --git a/frappe/public/css/desk.css b/frappe/public/css/desk.css index 6e7b9768c2..76b38ba164 100644 --- a/frappe/public/css/desk.css +++ b/frappe/public/css/desk.css @@ -440,6 +440,9 @@ fieldset[disabled] .form-control { top: 26px; } } +.barcode-wrapper { + text-align: center; +} @media (min-width: 768px) { .video-modal .modal-dialog { width: 700px; diff --git a/frappe/public/css/desktop.css b/frappe/public/css/desktop.css index 32bbd19c47..8b918ca8fe 100644 --- a/frappe/public/css/desktop.css +++ b/frappe/public/css/desktop.css @@ -59,7 +59,6 @@ body[data-route="desktop"] .navbar-default { width: 32px; } .app-icon path { - fill: #fafbfc; transition: 0.2s; -webkit-transition: 0.2s; } @@ -80,9 +79,6 @@ body[data-route="desktop"] .navbar-default { letter-spacing: normal; cursor: pointer; } -.app-icon:hover path { - fill: #fff; -} .app-icon:hover i, .app-icon:hover { color: #fff; diff --git a/frappe/public/css/form.css b/frappe/public/css/form.css index 255c2afc2a..57cbfb50b3 100644 --- a/frappe/public/css/form.css +++ b/frappe/public/css/form.css @@ -718,3 +718,12 @@ select.form-control { body[data-route^="Form/Communication"] textarea[data-fieldname="subject"] { height: 80px !important; } +.frappe-control[data-fieldtype="Attach"] .attached-file { + position: relative; + margin-top: 5px; +} +.frappe-control[data-fieldtype="Attach"] .attached-file .close { + position: absolute; + top: 0; + right: 0; +} diff --git a/frappe/public/css/page.css b/frappe/public/css/page.css index 5dae59687c..d856222fbc 100644 --- a/frappe/public/css/page.css +++ b/frappe/public/css/page.css @@ -281,6 +281,9 @@ select.input-sm { .setup-state { background-color: #f5f7fa; } +.page-container .page-card-container { + background-color: #fff; +} .page-card-container { padding: 70px; } diff --git a/frappe/public/css/report-rtl.css b/frappe/public/css/report-rtl.css index 26709a55e3..03e986c56b 100644 --- a/frappe/public/css/report-rtl.css +++ b/frappe/public/css/report-rtl.css @@ -2,10 +2,14 @@ direction: ltr; } +.page-form .awesomplete > ul { + left: auto; +} + .chart_area{ direction: ltr; } .grid-report .show-zero{ - direction: rtl ; + direction: rtl; } diff --git a/frappe/public/js/frappe/db.js b/frappe/public/js/frappe/db.js index 101228de97..3cc34e3f79 100644 --- a/frappe/public/js/frappe/db.js +++ b/frappe/public/js/frappe/db.js @@ -2,6 +2,13 @@ // MIT License. See license.txt frappe.db = { + exists: function(doctype, name) { + return new Promise ((resolve) => { + frappe.db.get_value(doctype, {name: name}, 'name').then((r) => { + (r.message && r.message.name) ? resolve(true) : resolve(false); + }); + }); + }, get_value: function(doctype, filters, fieldname, callback) { return frappe.call({ method: "frappe.client.get_value", diff --git a/frappe/public/js/frappe/form/controls/attach.js b/frappe/public/js/frappe/form/controls/attach.js index 99bef4a113..fed5fbe172 100644 --- a/frappe/public/js/frappe/form/controls/attach.js +++ b/frappe/public/js/frappe/form/controls/attach.js @@ -7,12 +7,14 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({ .on("click", function() { me.onclick(); }); - this.$value = $('
\ -
\ - \ - \ -
\ - ×
') + this.$value = $( + `
+
+ + +
+ × +
`) .prependTo(me.input_area) .toggle(false); this.input = this.$input.get(0); @@ -156,7 +158,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({ var filename = parts[0]; dataurl = parts[1]; } - this.$value.toggle(true).find(".attached-file") + this.$value.toggle(true).find(".attached-file-link") .html(filename || this.value) .attr("href", dataurl || this.value); } else { diff --git a/frappe/public/js/frappe/form/controls/autocomplete.js b/frappe/public/js/frappe/form/controls/autocomplete.js new file mode 100644 index 0000000000..b898860f23 --- /dev/null +++ b/frappe/public/js/frappe/form/controls/autocomplete.js @@ -0,0 +1,58 @@ +frappe.ui.form.ControlAutocomplete = frappe.ui.form.ControlData.extend({ + make_input() { + this._super(); + this.setup_awesomplete(); + }, + + setup_awesomplete() { + var me = this; + + this.awesomplete = new Awesomplete(this.input, { + minChars: 0, + maxItems: 99, + autoFirst: true, + list: this.get_data(), + data: function (item) { + if (typeof item === 'string') { + item = { + label: item, + value: item + }; + } + + return { + label: item.label || item.value, + value: item.value + }; + } + }); + + $(this.input_area).find('.awesomplete ul').css('min-width', '100%'); + + this.$input.on('input', () => { + this.awesomplete.list = this.get_data(); + }); + + this.$input.on('focus', () => { + if (!this.$input.val()) { + this.$input.val(''); + this.$input.trigger('input'); + } + }); + + this.$input.on('awesomplete-selectcomplete', () => { + this.$input.trigger('change'); + }); + }, + + get_data() { + return this._data || []; + }, + + set_data(data) { + if (this.awesomplete) { + this.awesomplete.list = data; + } + this._data = data; + } +}); diff --git a/frappe/public/js/frappe/form/controls/barcode.js b/frappe/public/js/frappe/form/controls/barcode.js new file mode 100644 index 0000000000..8d17b59bdb --- /dev/null +++ b/frappe/public/js/frappe/form/controls/barcode.js @@ -0,0 +1,34 @@ +frappe.ui.form.ControlBarcode = frappe.ui.form.ControlData.extend({ + make_wrapper() { + // Create the elements for barcode area + this._super(); + + let $input_wrapper = this.$wrapper.find('.control-input-wrapper'); + this.barcode_area = $(`
`); + frappe.require("assets/frappe/js/lib/JsBarcode.all.min.js", () => { + this.barcode_area.appendTo($input_wrapper); + }); + }, + + parse(value) { + // Parse raw value + return this.get_barcode_html(value); + }, + + set_formatted_input(value) { + // Set values to display + const svg = value; + const barcode_value = $(svg).attr('data-barcode-value'); + + this.$input.val(barcode_value); + this.barcode_area.html(svg); + }, + + get_barcode_html(value) { + // Get svg + const svg = this.barcode_area.find('svg')[0]; + JsBarcode(svg, value, {height: 40}); + $(svg).attr('data-barcode-value', value); + return this.barcode_area.html(); + } +}); diff --git a/frappe/public/js/frappe/form/controls/base_control.js b/frappe/public/js/frappe/form/controls/base_control.js index 68cd4b0da9..68a1138584 100644 --- a/frappe/public/js/frappe/form/controls/base_control.js +++ b/frappe/public/js/frappe/form/controls/base_control.js @@ -47,6 +47,10 @@ frappe.ui.form.Control = Class.extend({ // returns "Read", "Write" or "None" // as strings based on permissions get_status: function(explain) { + if (this.df.get_status) { + return this.df.get_status(this); + } + if(!this.doctype && !this.docname) { // like in case of a dialog box if (cint(this.df.hidden)) { diff --git a/frappe/public/js/frappe/form/controls/base_input.js b/frappe/public/js/frappe/form/controls/base_input.js index 5e93009202..99edde9936 100644 --- a/frappe/public/js/frappe/form/controls/base_input.js +++ b/frappe/public/js/frappe/form/controls/base_input.js @@ -71,7 +71,7 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ } }; - if(me.disp_status != "None") { + if (me.disp_status != "None") { // refresh value if(me.doctype && me.docname) { me.value = frappe.model.get_value(me.doctype, me.docname, me.df.fieldname); diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index 5751451f27..aa78f2df8e 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -28,9 +28,9 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ setup_autoname_check: function() { if (!this.df.parent) return; this.meta = frappe.get_meta(this.df.parent); - if (this.meta && this.meta.autoname + if (this.meta && ((this.meta.autoname && this.meta.autoname.substr(0, 6)==='field:' - && this.meta.autoname.substr(6) === this.df.fieldname) { + && this.meta.autoname.substr(6) === this.df.fieldname) || this.df.fieldname==='__newname') ) { this.$input.on('keyup', () => { this.set_description(''); if (this.doc && this.doc.__islocal) { diff --git a/frappe/public/js/frappe/form/controls/password.js b/frappe/public/js/frappe/form/controls/password.js index 5ea940d577..8a25642737 100644 --- a/frappe/public/js/frappe/form/controls/password.js +++ b/frappe/public/js/frappe/form/controls/password.js @@ -12,13 +12,11 @@ frappe.ui.form.ControlPassword = frappe.ui.form.ControlData.extend({ this.indicator = this.$wrapper.find('.password-strength-indicator'); this.message = this.$wrapper.find('.help-box'); - this.$input.on('input', () => { - var $this = $(this); - clearTimeout($this.data('timeout')); - $this.data('timeout', setTimeout(() => { - var txt = me.$input.val(); - me.get_password_strength(txt); - }), 300); + this.$input.on('keyup', () => { + clearTimeout(this.check_password_timeout); + this.check_password_timeout = setTimeout (() => { + me.get_password_strength(me.$input.val()); + }, 500); }); }, get_password_strength: function(value) { diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index ec96d2a51a..9c84a07af0 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -11,7 +11,7 @@ frappe.ui.form.Dashboard = Class.extend({ this.progress_area = this.wrapper.find(".progress-area"); this.heatmap_area = this.wrapper.find('.form-heatmap'); - this.graph_area = this.wrapper.find('.form-graph'); + this.chart_area = this.wrapper.find('.form-graph'); this.stats_area = this.wrapper.find('.form-stats'); this.stats_area_row = this.stats_area.find('.row'); this.links_area = this.wrapper.find('.form-links'); @@ -334,11 +334,12 @@ frappe.ui.form.Dashboard = Class.extend({ // heatmap render_heatmap: function() { if(!this.heatmap) { - this.heatmap = new frappe.ui.HeatMap({ - parent: this.heatmap_area.find("#heatmap-" + frappe.model.scrub(this.frm.doctype)), + this.heatmap = new frappe.chart.FrappeChart({ + parent: "#heatmap-" + frappe.model.scrub(this.frm.doctype), + type: 'heatmap', height: 100, start: new Date(moment().subtract(1, 'year').toDate()), - count_label: "items", + count_label: frappe.model.scrub(this.frm.doctype) + "s", discrete_domains: 0 }); @@ -403,16 +404,25 @@ frappe.ui.form.Dashboard = Class.extend({ render_graph: function(args) { var me = this; - this.graph_area.empty().removeClass('hidden'); + this.chart_area.empty().removeClass('hidden'); $.extend(args, { - parent: me.graph_area, - mode: 'line', + parent: '.form-graph', + type: 'line', height: 140 }); - new frappe.ui.Graph(args); + this.show(); + + this.chart = new frappe.chart.FrappeChart(args); + if(!this.chart) { + this.hide(); + } }, show: function() { this.section.removeClass('hidden'); + }, + + hide: function() { + this.section.addClass('hidden'); } }); diff --git a/frappe/public/js/frappe/form/sidebar.js b/frappe/public/js/frappe/form/form_sidebar.js similarity index 100% rename from frappe/public/js/frappe/form/sidebar.js rename to frappe/public/js/frappe/form/form_sidebar.js diff --git a/frappe/public/js/frappe/form/form_viewers.js b/frappe/public/js/frappe/form/form_viewers.js index f6bd8b847a..28965c01c1 100644 --- a/frappe/public/js/frappe/form/form_viewers.js +++ b/frappe/public/js/frappe/form/form_viewers.js @@ -74,7 +74,7 @@ frappe.ui.form.set_viewers = function(data) { current: viewers }); - if (cur_frm.doc && cur_frm.doc.doctype===doctype && cur_frm.doc.name==docname) { + if (cur_frm && cur_frm.doc && cur_frm.doc.doctype===doctype && cur_frm.doc.name==docname) { cur_frm.viewers.refresh(true); } } diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 63162692cc..b9987b38e8 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -350,13 +350,53 @@ frappe.ui.form.Grid = Class.extend({ for(var i=0, l=fieldname.length; i').appendTo(this.parent); this.message = $('').appendTo(this.wrapper); if(!this.fields) { - this.fields = frappe.meta.sort_docfields(frappe.meta.docfield_map[this.doctype]); + this.fields = this.get_doctype_fields(); } this.setup_tabbing(); this.render(); @@ -35,6 +35,28 @@ frappe.ui.form.Layout = Class.extend({ this.show_message(__("This form does not have any input")); } }, + get_doctype_fields: function() { + let fields = [ + { + parent: this.frm.doctype, + fieldtype: 'Data', + fieldname: '__newname', + reqd: 1, + hidden: 1, + label: __('Name'), + get_status: function(field) { + if (field.frm && field.frm.is_new() + && field.frm.meta.autoname + && ['prompt', 'name'].includes(field.frm.meta.autoname.toLowerCase())) { + return 'Write'; + } + return 'None'; + } + } + ]; + fields = fields.concat(frappe.meta.sort_docfields(frappe.meta.docfield_map[this.doctype])); + return fields; + }, show_message: function(html) { if(html) { if(html.substr(0, 1)!=='<') { diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js index 015cc27ded..f3f5e55d71 100644 --- a/frappe/public/js/frappe/form/quick_entry.js +++ b/frappe/public/js/frappe/form/quick_entry.js @@ -1,6 +1,6 @@ frappe.provide('frappe.ui.form'); -frappe.ui.form.make_quick_entry = (doctype, after_insert, init_callback) => { +frappe.ui.form.make_quick_entry = (doctype, after_insert, init_callback, doc) => { var trimmed_doctype = doctype.replace(/ /g, ''); var controller_name = "QuickEntryForm"; @@ -8,15 +8,16 @@ frappe.ui.form.make_quick_entry = (doctype, after_insert, init_callback) => { controller_name = trimmed_doctype + "QuickEntryForm"; } - frappe.quick_entry = new frappe.ui.form[controller_name](doctype, after_insert, init_callback); + frappe.quick_entry = new frappe.ui.form[controller_name](doctype, after_insert, init_callback, doc); return frappe.quick_entry.setup(); }; frappe.ui.form.QuickEntryForm = Class.extend({ - init: function(doctype, after_insert, init_callback){ + init: function(doctype, after_insert, init_callback, doc) { this.doctype = doctype; this.after_insert = after_insert; this.init_callback = init_callback; + this.doc = doc; }, setup: function() { @@ -40,7 +41,9 @@ frappe.ui.form.QuickEntryForm = Class.extend({ this.mandatory = $.map(frappe.get_meta(this.doctype).fields, function(d) { return (d.reqd || d.bold && !d.read_only) ? d : null; }); this.meta = frappe.get_meta(this.doctype); - this.doc = frappe.model.get_new_doc(this.doctype, null, null, true); + if (!this.doc) { + this.doc = frappe.model.get_new_doc(this.doctype, null, null, true); + } }, is_quick_entry: function(){ @@ -108,7 +111,7 @@ frappe.ui.form.QuickEntryForm = Class.extend({ this.dialog.onhide = () => frappe.quick_entry = null; this.dialog.show(); this.set_defaults(); - + if (this.init_callback) { this.init_callback(this.dialog); } diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js index b6d535c329..3aadb8c314 100644 --- a/frappe/public/js/frappe/form/save.js +++ b/frappe/public/js/frappe/form/save.js @@ -18,27 +18,62 @@ frappe.ui.form.save = function (frm, action, callback, btn) { var freeze_message = working_label ? __(working_label) : ""; var save = function () { - check_name(function () { - $(frm.wrapper).addClass('validated-form'); - if (check_mandatory()) { - _call({ - method: "frappe.desk.form.save.savedocs", - args: { doc: frm.doc, action: action }, - callback: function (r) { - $(document).trigger("save", [frm.doc]); - callback(r); - }, - error: function (r) { - callback(r); - }, - btn: btn, - freeze_message: freeze_message - }); - } else { - $(btn).prop("disabled", false); - } + remove_empty_rows(); + $(frm.wrapper).addClass('validated-form'); + if (check_mandatory()) { + _call({ + method: "frappe.desk.form.save.savedocs", + args: { doc: frm.doc, action: action }, + callback: function (r) { + $(document).trigger("save", [frm.doc]); + callback(r); + }, + error: function (r) { + callback(r); + }, + btn: btn, + freeze_message: freeze_message + }); + } else { + $(btn).prop("disabled", false); + } + }; + + var remove_empty_rows = function() { + /** + This function removes empty rows. Note that in this function, a row is considered + empty if the fields with `in_list_view: 1` are undefined or falsy because that's + what users also consider to be an empty row + */ + const docs = frappe.model.get_all_docs(frm.doc); + + // we should only worry about table data + const tables = docs.filter(function(d){ + return frappe.model.is_table(d.doctype); }); + tables.map( + function(doc){ + const cells = frappe.meta.docfield_list[doc.doctype] || []; + + const in_list_view_cells = cells.filter(function(df) { + return cint(df.in_list_view) === 1; + }); + + var is_empty_row = function(cells) { + for (var i=0; i < cells.length; i++){ + if(locals[doc.doctype][doc.name][cells[i].fieldname]){ + return false; + } + } + return true; + } + + if (is_empty_row(in_list_view_cells)) { + frappe.model.clear_doc(doc.doctype, doc.name); + } + } + ); }; var cancel = function () { @@ -69,36 +104,6 @@ frappe.ui.form.save = function (frm, action, callback, btn) { }); }; - var check_name = function (callback) { - var doc = frm.doc; - var meta = locals.DocType[doc.doctype]; - if (doc.__islocal && (meta && meta.autoname - && meta.autoname.toLowerCase() == 'prompt')) { - var d = frappe.prompt(__("Name"), function (values) { - var newname = values.value; - if (newname) { - doc.__newname = strip(newname); - } else { - frappe.msgprint(__("Name is required")); - throw "name required"; - } - - callback(); - - }, __('Enter the name of the new {0}', [doc.doctype]), __("Create")); - - if (doc.__newname) { - d.set_value("value", doc.__newname); - } - - d.onhide = function () { - $(btn).prop("disabled", false); - } - } else { - callback(); - } - }; - var check_mandatory = function () { var me = this; var has_errors = false; diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index 4a9a5753c2..744a19f770 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -28,6 +28,12 @@ frappe.views.ListSidebar = Class.extend({ this.setup_views(); this.setup_kanban_boards(); this.setup_email_inbox(); + + let limits = frappe.boot.limits; + + if(limits.upgrade_url && limits.expiry && !frappe.flags.upgrade_dismissed) { + this.setup_upgrade_box(); + } }, setup_views: function() { var show_list_link = false; @@ -66,7 +72,7 @@ frappe.views.ListSidebar = Class.extend({ //enable link for Kanban view this.sidebar.find('.list-link[data-view="Kanban"] a, .list-link[data-view="Inbox"] a') - .attr('disabled', null).removeClass('disabled') + .attr('disabled', null).removeClass('disabled'); // show image link if image_view if(this.list_view.meta.image_field) { @@ -97,7 +103,7 @@ frappe.views.ListSidebar = Class.extend({ added.push(route); if(!divider) { - $('').appendTo(dropdown); + me.get_divider().appendTo(dropdown); divider = true; } @@ -129,7 +135,7 @@ frappe.views.ListSidebar = Class.extend({ boards.forEach(function(board) { var route = ["List", board.reference_doctype, "Kanban", board.name].join('/'); if(!divider) { - $('').appendTo($dropdown); + me.get_divider().appendTo($dropdown); divider = true; } $(`
  • @@ -270,30 +276,29 @@ frappe.views.ListSidebar = Class.extend({ if(this.doctype != "Communication") return; - var $dropdown = this.page.sidebar.find('.email-account-dropdown'); - var divider = false; + let $dropdown = this.page.sidebar.find('.email-account-dropdown'); + let divider = false; if(has_common(frappe.user_roles, ["System Manager", "Administrator"])) { - $('
  • ') + $(``) .appendTo($dropdown) } - var accounts = frappe.boot.email_accounts; - - accounts.forEach(function(account) { - var email_account = (account.email_id == "All Accounts")? "All Accounts": account.email_account; - var route = ["List", "Communication", "Inbox", email_account].join('/'); + let accounts = frappe.boot.email_accounts; + accounts.forEach((account) => { + let email_account = (account.email_id == "All Accounts")? "All Accounts": account.email_account; + let route = ["List", "Communication", "Inbox", email_account].join('/'); if(!divider) { - $('').appendTo($dropdown); + this.get_divider().appendTo($dropdown); divider = true; } - $('
  • '+account.email_id+'
  • ').appendTo($dropdown); + $(`
  • ${account.email_id}
  • `).appendTo($dropdown); if(account.email_id === "Sent Mail") divider = false }); $dropdown.find('.new-email-account').click(function() { - frappe.new_doc("Email Account") + frappe.new_doc("Email Account"); }); }, setup_assigned_to_me: function() { @@ -302,11 +307,32 @@ frappe.views.ListSidebar = Class.extend({ me.list_view.assigned_to_me(); }); }, + setup_upgrade_box: function() { + let upgrade_list = $(``).appendTo(this.sidebar); + let upgrade_box = $(`
    + +
    Go Premium
    +

    Upgrade to a premium plan with more users, storage and priority support.

    + +
    `).appendTo(upgrade_list); + + upgrade_box.find('.btn-primary').on('click', () => { + window.open(frappe.boot.limits.upgrade_url); + }); + + upgrade_box.find('.close').on('click', () => { + upgrade_list.remove(); + frappe.flags.upgrade_dismissed = 1; + }); + }, get_cat_tags:function(){ return this.cat_tags; }, get_stats: function() { - var me = this + var me = this; frappe.call({ method: 'frappe.desk.reportview.get_sidebar_stats', args: { @@ -421,4 +447,7 @@ frappe.views.ListSidebar = Class.extend({ this.sidebar.find(".sidebar-stat").remove(); this.get_stats(); }, + get_divider: function() { + return $(''); + } }); diff --git a/frappe/public/js/frappe/misc/common.js b/frappe/public/js/frappe/misc/common.js index c01921f6aa..264ca8aae0 100644 --- a/frappe/public/js/frappe/misc/common.js +++ b/frappe/public/js/frappe/misc/common.js @@ -50,6 +50,15 @@ frappe.avatar = function(user, css_class, title) { } } +frappe.ui.scroll = function(element, animate, additional_offset) { + var header_offset = $(".navbar").height() + $(".page-head").height(); + var top = $(element).offset().top - header_offset - cint(additional_offset); + if (animate) { + $("html, body").animate({ scrollTop: top }); + } else { + $(window).scrollTop(top); + } +}; frappe.get_palette = function(txt) { return '#fafbfc'; diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index 0a58c410b5..81f0249480 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -327,17 +327,23 @@ $.extend(frappe.model, { set_value: function(doctype, docname, fieldname, value, fieldtype) { /* help: Set a value locally (if changed) and execute triggers */ - var doc = locals[doctype] && locals[doctype][docname]; + var doc; + if ($.isPlainObject(doctype)) { + // first parameter is the doc, shift parameters to the left + doc = doctype; fieldname = docname; value = fieldname; + } else { + doc = locals[doctype] && locals[doctype][docname]; + } - var to_update = fieldname; + let to_update = fieldname; let tasks = []; if(!$.isPlainObject(to_update)) { to_update = {}; to_update[fieldname] = value; } - $.each(to_update, function(key, value) { - if(doc && doc[key] !== value) { + $.each(to_update, (key, value) => { + if (doc && doc[key] !== value) { if(doc.__unedited && !(!doc[key] && !value)) { // unset unedited flag for virgin rows doc.__unedited = false; diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js index bde19db1f3..de474538ab 100644 --- a/frappe/public/js/frappe/request.js +++ b/frappe/public/js/frappe/request.js @@ -8,10 +8,16 @@ frappe.request.url = '/'; frappe.request.ajax_count = 0; frappe.request.waiting_for_ajax = []; - - // generic server call (call page, object) frappe.call = function(opts) { + if (typeof arguments[0]==='string') { + opts = { + method: arguments[0], + args: arguments[1], + callback: arguments[2] + } + } + if(opts.quiet) { opts.no_spinner = true; } @@ -139,6 +145,7 @@ frappe.request.call = function(opts) { frappe.msgprint({message:__("Server Error: Please check your server logs or contact tech support."), title:__('Something went wrong'), indicator: 'red'}); try { opts.error_callback && opts.error_callback(); + frappe.request.report_error(xhr, opts); } catch (e) { frappe.request.report_error(xhr, opts); } diff --git a/frappe/public/js/frappe/socketio_client.js b/frappe/public/js/frappe/socketio_client.js index c7dd77594f..2c4477e1e6 100644 --- a/frappe/public/js/frappe/socketio_client.js +++ b/frappe/public/js/frappe/socketio_client.js @@ -222,17 +222,19 @@ frappe.socketio = { }, 5); }); // js files show alert - frappe.socketio.file_watcher.on('reload_js', function(filename) { - filename = "assets/" + filename; - var msg = $(` - ${filename} changed Click to Reload - `) - msg.find('a').click(frappe.ui.toolbar.clear_cache); - frappe.show_alert({ - indicator: 'orange', - message: msg - }, 5); - }); + + // commenting as this kills a branch change + // frappe.socketio.file_watcher.on('reload_js', function(filename) { + // filename = "assets/" + filename; + // var msg = $(` + // ${filename} changed Click to Reload + // `) + // msg.find('a').click(frappe.ui.toolbar.clear_cache); + // frappe.show_alert({ + // indicator: 'orange', + // message: msg + // }, 5); + // }); }, process_response: function(data, method) { if(!data) { @@ -296,12 +298,12 @@ frappe.socketio.SocketIOUploader = class SocketIOUploader { }); frappe.socketio.socket.on('upload-end', (data) => { + this.reader = null; + this.file = null; if (data.file_url.substr(0, 7)==='/public') { data.file_url = data.file_url.substr(7); } this.callback(data); - this.reader = null; - this.file = null; }); frappe.socketio.socket.on('upload-error', (data) => { diff --git a/frappe/public/js/frappe/toolbar.js b/frappe/public/js/frappe/toolbar.js index a6a8931001..a466e038bd 100755 --- a/frappe/public/js/frappe/toolbar.js +++ b/frappe/public/js/frappe/toolbar.js @@ -17,15 +17,10 @@ $(document).on("toolbar_setup", function() { } } - if(limits.support_email || limits.support_chat) { + if(limits.support_chat) { help_links.push('
  • '); } - if(limits.support_email) { - var support_link = 'mailto:'+frappe.boot.limits.support_email; - help_links.push('
  • ' + frappe._('Email Support') + '
  • '); - } - if(limits.support_chat) { help_links.push('
  • ' + frappe._('Chat Support') + '
  • '); } diff --git a/frappe/public/js/frappe/ui/charts.js b/frappe/public/js/frappe/ui/charts.js index 054f1bdc36..8927f2e21b 100644 --- a/frappe/public/js/frappe/ui/charts.js +++ b/frappe/public/js/frappe/ui/charts.js @@ -1,109 +1,1556 @@ -frappe.provide("frappe.ui"); - -frappe.ui.Chart = Class.extend({ - init: function(opts) { - this.opts = {}; - $.extend(this.opts, opts); - this.show_chart(false); - - $(this.opts.wrapper).html('' + - '
    ' + - '
    '); - - this.setup_events(); - - this.opts.bind_to = frappe.dom.set_unique_id(this.opts.wrapper.find(".chart_area_result")); - - if(this.opts.data && ((this.opts.data.columns && this.opts.data.columns.length >= 1) - || (this.opts.data.rows && this.opts.data.rows.length >= 1))) { - this.chart = this.render_chart(); - this.show_chart(true); - } - - return this.chart; - }, - - render_chart: function() { - var chart_dict = { - bindto: '#' + this.opts.bind_to, - data: {}, - axis: { - x: { - type: this.opts.x_type || 'category' // this needed to load string x value - }, - y: { - padding: { bottom: 10 } +// specific_values = [ +// { +// title: "Average", +// line_type: "dashed", // "dashed" or "solid" +// value: 10 +// }, + +// summary = [ +// { +// title: "Total", +// color: 'blue', // Indicator colors: 'grey', 'blue', 'red', 'green', 'orange', +// // 'purple', 'darkgrey', 'black', 'yellow', 'lightblue' +// value: 80 +// } +// ] + +// Validate all arguments, check passed data format, set defaults + +frappe.provide("frappe.chart"); + +frappe.chart.FrappeChart = class { + constructor({ + parent = "", + height = 240, + + title = '', subtitle = '', + + data = {}, + format_lambdas = {}, + + specific_values = [], + summary = [], + + is_navigable = 0, + + type = '' + }) { + if(Object.getPrototypeOf(this) === frappe.chart.FrappeChart.prototype) { + if(type === 'line') { + return new frappe.chart.LineChart(arguments[0]); + } else if(type === 'bar') { + return new frappe.chart.BarChart(arguments[0]); + } else if(type === 'percentage') { + return new frappe.chart.PercentageChart(arguments[0]); + } else if(type === 'heatmap') { + return new frappe.chart.HeatMap(arguments[0]); + } + } + + this.parent = document.querySelector(parent); + this.title = title; + this.subtitle = subtitle; + + this.data = data; + this.format_lambdas = format_lambdas; + + this.specific_values = specific_values; + this.summary = summary; + + this.is_navigable = is_navigable; + if(this.is_navigable) { + this.current_index = 0; + } + + this.set_margins(height); + } + + set_margins(height) { + this.base_height = height; + this.height = height - 40; + this.translate_x = 60; + this.translate_y = 10; + } + + setup() { + this.bind_window_events(); + this.refresh(); + } + + bind_window_events() { + window.addEventListener('resize', () => this.refresh()); + window.addEventListener('orientationchange', () => this.refresh()); + } + + refresh() { + this.setup_base_values(); + this.set_width(); + + this.setup_container(); + this.setup_components(); + + this.setup_values(); + this.setup_utils(); + + this.make_graph_components(); + this.make_tooltip(); + + if(this.summary.length > 0) { + this.show_custom_summary(); + } else { + this.show_summary(); + } + + if(this.is_navigable) { + this.setup_navigation(); + } + } + + set_width() { + let special_values_width = 0; + this.specific_values.map(val => { + if(this.get_strwidth(val.title) > special_values_width) { + special_values_width = this.get_strwidth(val.title); + } + }); + this.base_width = this.parent.offsetWidth - special_values_width; + this.width = this.base_width - this.translate_x * 2; + } + + setup_base_values() {} + + setup_container() { + this.container = $$.create('div', { + className: 'chart-container', + innerHTML: `
    ${this.title}
    +
    ${this.subtitle}
    +
    +
    ` + }); + + // Chart needs a dedicated parent element + this.parent.innerHTML = ''; + this.parent.appendChild(this.container); + + this.chart_wrapper = this.container.querySelector('.frappe-chart'); + // this.chart_wrapper.appendChild(); + + this.make_chart_area(); + this.make_draw_area(); + + this.stats_wrapper = this.container.querySelector('.graph-stats-container'); + } + + make_chart_area() { + this.svg = $$.createSVG('svg', { + className: 'chart', + inside: this.chart_wrapper, + width: this.base_width, + height: this.base_height + }); + + return this.svg; + } + + make_draw_area() { + this.draw_area = $$.createSVG("g", { + className: this.type, + inside: this.svg, + transform: `translate(${this.translate_x}, ${this.translate_y})` + }); + } + + setup_components() { + this.svg_units_group = $$.createSVG('g', { + className: 'data-points', + inside: this.draw_area + }); + } + + make_tooltip() { + this.tip = new frappe.chart.SvgTip({ + parent: this.chart_wrapper, + }); + this.bind_tooltip(); + } + + + show_summary() {} + show_custom_summary() { + this.summary.map(d => { + let stats = $$.create('div', { + className: 'stats', + innerHTML: `${d.title}: ${d.value}` + }); + this.stats_wrapper.appendChild(stats); + }); + } + + setup_navigation() { + this.make_overlay(); + this.bind_overlay(); + document.onkeydown = (e) => { + e = e || window.event; + + if (e.keyCode == '37') { + this.on_left_arrow(); + } else if (e.keyCode == '39') { + this.on_right_arrow(); + } else if (e.keyCode == '38') { + this.on_up_arrow(); + } else if (e.keyCode == '40') { + this.on_down_arrow(); + } else if (e.keyCode == '13') { + this.on_enter_key(); + } + }; + } + + make_overlay() {} + bind_overlay() {} + + on_left_arrow() {} + on_right_arrow() {} + on_up_arrow() {} + on_down_arrow() {} + on_enter_key() {} + + get_data_point(index=this.current_index) { + // check for length + let data_point = { + index: index + }; + let y = this.y[0]; + ['svg_units', 'y_tops', 'values'].map(key => { + let data_key = key.slice(0, key.length-1); + data_point[data_key] = y[key][index]; + }); + data_point.label = this.x[index]; + return data_point; + } + + update_current_data_point(index) { + if(index < 0) index = 0; + if(index >= this.x.length) index = this.x.length - 1; + if(index === this.current_index) return; + this.current_index = index; + $$.fire(this.parent, "data-select", this.get_data_point()); + } + + // Helpers + get_strwidth(string) { + return string.length * 8; + } + + // Objects + setup_utils() { + this.draw = { + 'bar': (x, y, args, color, index) => { + let total_width = this.avg_unit_width - args.space_width; + let start_x = x - total_width/2; + + let width = total_width / args.no_of_datasets; + let current_x = start_x + width * index; + if(y == this.height) { + y = this.height * 0.98; } + return $$.createSVG('rect', { + className: `bar mini fill ${color}`, + x: current_x, + y: y, + width: width, + height: this.height - y + }); + }, - padding: { - left: 60, - top: 30, - right: 30, - bottom: 10 - }, - pie: { - expand : false + 'dot': (x, y, args, color) => { + return $$.createSVG('circle', { + className: `fill ${color}`, + cx: x, + cy: y, + r: args.radius + }); + } + }; + + this.animate = { + 'bar': (bar, new_y, args) => { + return [bar, {height: args.new_height, y: new_y}, 300, "easein"]; + // bar.animate({height: args.new_height, y: new_y}, 300, mina.easein); }, - bar: { - "width": 10 + 'dot': (dot, new_y) => { + return [dot, {cy: new_y}, 300, "easein"]; + // dot.animate({cy: new_y}, 300, mina.easein); } }; + } +} - $.extend(chart_dict, this.opts); +frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart { + constructor(args) { + super(args); - chart_dict["data"]["type"] = this.opts.chart_type || "line"; + this.x = this.data.labels; + this.y = this.data.datasets; + + this.get_x_label = this.format_lambdas.x_label; + this.get_y_label = this.format_lambdas.y_label; + this.get_x_tooltip = this.format_lambdas.x_tooltip; + this.get_y_tooltip = this.format_lambdas.y_tooltip; + + this.colors = ['lightblue', 'purple', 'blue', 'green', 'lightgreen', + 'yellow', 'orange', 'red']; + } - if(this.opts.x_type==='timeseries') { - if(!chart_dict.axis.x.tick) { - chart_dict.axis.x.tick = {} + setup_values() { + this.data.datasets.map(d => { + d.values = d.values.map(val => (!isNaN(val) ? val : 0)); + }); + this.setup_x(); + this.setup_y(); + } + + setup_x() { + this.set_avg_unit_width_and_x_offset(); + this.x_axis_values = this.x.map((d, i) => frappe.chart.utils.float_2(this.x_offset + i * this.avg_unit_width)); + } + + setup_y() { + this.setup_metrics(); + this.y_axis_values = this.get_y_axis_values(this.upper_limit, this.parts); + } + + setup_components() { + this.y_axis_group = $$.createSVG('g', {className: 'y axis', inside: this.draw_area}); + this.x_axis_group = $$.createSVG('g', {className: 'x axis', inside: this.draw_area}); + this.specific_y_lines = $$.createSVG('g', {className: 'specific axis', inside: this.draw_area}); + super.setup_components(); + } + + make_graph_components() { + this.make_y_axis(); + this.make_x_axis(); + this.draw_graph(); + this.make_y_specifics(); + } + + // make HORIZONTAL lines for y values + make_y_axis() { + if(this.y_axis_group.textContent) { + // animate from old to new, both elemnets + } else { + // only new + } + + this.y_axis_group.textContent = ''; + + let width, text_end_at = -9, label_class = '', start_at = 0; + if(this.y_axis_mode === 'span') { // long spanning lines + width = this.width + 6; + start_at = -6; + } else if(this.y_axis_mode === 'tick'){ // short label lines + width = -6; + label_class = 'y-axis-label'; + } + + this.y_axis_values.map((point) => { + let line = $$.createSVG('line', { + x1: start_at, + x2: width, + y1: 0, + y2: 0 + }); + let text = $$.createSVG('text', { + className: 'y-value-text', + x: text_end_at, + y: 0, + dy: '.32em', + innerHTML: point+"" + }); + + let y_level = $$.createSVG('g', { + className: `tick ${label_class}`, + transform: `translate(0, ${this.height - point * this.multiplier })` + }); + + y_level.appendChild(line); + y_level.appendChild(text); + + this.y_axis_group.appendChild(y_level); + }); + } + + // make VERTICAL lines for x values + make_x_axis() { + let start_at, height, text_start_at, label_class = ''; + if(this.x_axis_mode === 'span') { // long spanning lines + start_at = -7; + height = this.height + 15; + text_start_at = this.height + 25; + } else if(this.x_axis_mode === 'tick'){ // short label lines + start_at = this.height; + height = 6; + text_start_at = 9; + label_class = 'x-axis-label'; + } + + this.x_axis_group.setAttribute('transform', `translate(0,${start_at})`); + + this.x.map((point, i) => { + let allowed_space = this.avg_unit_width * 1.5; + if(this.get_strwidth(point) > allowed_space) { + let allowed_letters = allowed_space / 8; + point = point.slice(0, allowed_letters-3) + " ..."; + } + + let line = $$.createSVG('line', { + x1: 0, + x2: 0, + y1: 0, + y2: height + }); + let text = $$.createSVG('text', { + className: 'x-value-text', + x: 0, + y: text_start_at, + dy: '.71em', + innerHTML: point + }); + + let x_level = $$.createSVG('g', { + className: `tick ${label_class}`, + transform: `translate(${ this.x_axis_values[i] }, 0)` + }); + + x_level.appendChild(line); + x_level.appendChild(text); + + this.x_axis_group.appendChild(x_level); + }); + } + + draw_graph() { + // TODO: Don't animate on refresh + let data = []; + this.svg_units_group.textContent = ''; + this.y.map((d, i) => { + // Anim: Don't draw initial values, store them and update later + d.y_tops = new Array(d.values.length).fill(this.height); // no value + data.push({values: d.values}); + d.svg_units = []; + + this.make_new_units_for_dataset(d.y_tops, d.color || this.colors[i], i); + this.make_path && this.make_path(d, d.color || this.colors[i]); + }); + + // Data points + // this.calc_all_y_tops(); + // this.calc_min_tops(); + + setTimeout(() => { + this.update_values(data); + }, 500); + } + + setup_navigation() { + // Hack: defer nav till initial update_values + setTimeout(() => { + super.setup_navigation(); + }, 1000); + } + + make_new_units_for_dataset(y_values, color, dataset_index) { + this.y[dataset_index].svg_units = []; + + let d = this.unit_args; + y_values.map((y, i) => { + let data_unit = this.draw[d.type]( + this.x_axis_values[i], + y, + d.args, + color, + dataset_index + ); + this.svg_units_group.appendChild(data_unit); + this.y[dataset_index].svg_units.push(data_unit); + }); + } + + make_y_specifics() { + this.specific_values.map(d => { + let line = $$.createSVG('line', { + className: d.line_type === "dashed" ? "dashed": "", + x1: 0, + x2: this.width, + y1: 0, + y2: 0 + }); + + let text = $$.createSVG('text', { + className: 'specific-value', + x: this.width + 5, + y: 0, + dy: '.32em', + innerHTML: d.title.toUpperCase() + }); + + let specific_y_level = $$.createSVG('g', { + className: `tick`, + transform: `translate(0, ${this.height - d.value * this.multiplier })` + }); + + specific_y_level.appendChild(line); + specific_y_level.appendChild(text); + + this.specific_y_lines.appendChild(specific_y_level); + }); + } + + bind_tooltip() { + // should be w.r.t. this.parent, but will have to take care of + // all the elements and padding, margins on top + this.chart_wrapper.addEventListener('mousemove', (e) => { + let rect = this.chart_wrapper.getBoundingClientRect(); + let offset = { + top: rect.top + document.body.scrollTop, + left: rect.left + document.body.scrollLeft + } + let relX = e.pageX - offset.left - this.translate_x; + let relY = e.pageY - offset.top - this.translate_y; + + if(relY < this.height + this.translate_y * 2) { + this.map_tooltip_x_position_and_show(relX); + } else { + this.tip.hide_tip(); + } + }); + } + + map_tooltip_x_position_and_show(relX) { + for(var i=this.x_axis_values.length - 1; i >= 0 ; i--) { + let x_val = this.x_axis_values[i]; + // let delta = i === 0 ? this.avg_unit_width : x_val - this.x_axis_values[i-1]; + if(relX > x_val - this.avg_unit_width/2) { + let x = x_val + this.translate_x - 0.5; + let y = this.y_min_tops[i] + this.translate_y + 4; // adjustment + + let title = this.x.formatted && this.x.formatted.length>0 + ? this.x.formatted[i] : this.x[i]; + let values = this.y.map((set, j) => { + return { + title: set.title, + value: set.formatted ? set.formatted[i] : set.values[i], + color: set.color || this.colors[j], + } + }); + + this.tip.set_values(x, y, title, '', values); + this.tip.show_tip(); + break; } - chart_dict.axis.x.tick.culling = {max: 15}; - chart_dict.axis.x.tick.format = frappe.boot.sysdefaults.date_format - .replace('yyyy', '%Y').replace('mm', '%m').replace('dd', '%d'); } + } + + // API + update_values(new_y) { + // Just update values prop, setup_y() will do the rest + this.y.map((d, i) => {d.values = new_y[i].values;}); + + let old_upper_limit = this.upper_limit; + this.setup_y(); + if(old_upper_limit !== this.upper_limit){ + this.make_y_axis(); + } + + let elements_to_animate = []; + elements_to_animate = this.animate_for_equilength_data(elements_to_animate); - // set color - if(!chart_dict.data.colors && chart_dict.data.columns) { - var colors = ['#4E50A6', '#7679FB', '#A3A5FC', '#925191', '#5D3EA4', '#8D5FFA', - '#5E3AA8', '#7B933D', '#4F8EA8']; - chart_dict.data.colors = {}; - chart_dict.data.columns.forEach(function(d, i) { - if(d[0]!=='x') { - chart_dict.data.colors[d[0]] = colors[i-1]; + // create new x,y pair string and animate path + if(this.y[0].path) { + this.y.map((e, i) => { + let new_points_list = e.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y)); + let new_path_str = "M"+new_points_list.join("L"); + let args = [{unit:this.y[i].path, object: this.y[i], key:'path'}, {d:new_path_str}, 300, "easein"]; + elements_to_animate.push(args); + }); + } + + // elements_to_animate = elements_to_animate.concat(this.update_y_axis()); + let anim_svg = $$.runSVGAnimation(this.svg, elements_to_animate); + this.chart_wrapper.innerHTML = ''; + this.chart_wrapper.appendChild(anim_svg); + + // Replace the new svg (data has long been replaced) + setTimeout(() => { + this.chart_wrapper.innerHTML = ''; + this.chart_wrapper.appendChild(this.svg); + }, 250); + } + + update_y_axis() { + let elements = []; + + return elements; + } + + update_x_axis() { + // update + } + + animate_for_equilength_data(elements_to_animate) { + this.y.map((d) => { + d.y_tops = d.values.map(val => frappe.chart.utils.float_2(this.height - val * this.multiplier)); + d.svg_units.map((unit, j) => { + elements_to_animate.push(this.animate[this.unit_args.type]( + {unit:unit, array:d.svg_units, index: j}, // unit, with info to replace from data + d.y_tops[j], + {new_height: this.height - d.y_tops[j]} + )); + }); + }); + this.calc_min_tops(); + return elements_to_animate; + } + + add_data_point(data_point) { + this.x.push(data_point.label); + this.y.values.push(); + } + + // Helpers + get_upper_limit_and_parts(array) { + let max_val = parseInt(Math.max(...array)); + if((max_val+"").length <= 1) { + return [10, 5]; + } else { + let multiplier = Math.pow(10, ((max_val+"").length - 1)); + let significant = Math.ceil(max_val/multiplier); + if(significant % 2 !== 0) significant++; + let parts = (significant < 5) ? significant : significant/2; + return [significant * multiplier, parts]; + } + } + + get_y_axis_values(upper_limit, parts) { + let y_axis = []; + for(var i = 0; i <= parts; i++){ + y_axis.push(upper_limit / parts * i); + } + return y_axis; + } + + set_avg_unit_width_and_x_offset() { + this.avg_unit_width = this.width/(this.x.length - 1); + this.x_offset = 0; + } + + setup_metrics() { + // Metrics: upper limit, no. of parts, multiplier + let values = this.get_all_y_values(); + [this.upper_limit, this.parts] = this.get_upper_limit_and_parts(values); + this.multiplier = this.height / this.upper_limit; + } + + get_all_y_values() { + let all_values = []; + this.y.map(d => { + all_values = all_values.concat(d.values); + }); + return all_values.concat(this.specific_values.map(d => d.value)); + } + + calc_all_y_tops() { + this.y.map(d => { + d.y_tops = d.values.map( val => frappe.chart.utils.float_2(this.height - val * this.multiplier)); + }); + } + + calc_min_tops() { + this.y_min_tops = new Array(this.x_axis_values.length).fill(9999); + this.y.map(d => { + d.y_tops.map( (y_top, i) => { + if(y_top < this.y_min_tops[i]) { + this.y_min_tops[i] = y_top; } }); + }); + } +} + +frappe.chart.BarChart = class BarChart extends frappe.chart.AxisChart { + constructor() { + super(arguments[0]); + + this.type = 'bar-graph'; + this.setup(); + } + + setup_values() { + super.setup_values(); + this.x_offset = this.avg_unit_width; + this.y_axis_mode = 'span'; + this.x_axis_mode = 'tick'; + this.unit_args = { + type: 'bar', + args: { + space_width: this.avg_unit_width/2, + no_of_datasets: this.y.length + } + }; + } + + make_overlay() { + // Just make one out of the first element + let unit = this.y[0].svg_units[0]; + + this.overlay = unit.cloneNode(); + this.overlay.style.fill = '#000000'; + this.overlay.style.opacity = '0.4'; + this.draw_area.appendChild(this.overlay); + } + + bind_overlay() { + // on event, update overlay + this.parent.addEventListener('data-select', (e) => { + this.update_overlay(e.svg_unit); + }); + } + + update_overlay(unit) { + let attributes = []; + Object.keys(unit.attributes).map(index => { + attributes.push(unit.attributes[index]); + }); + + attributes.filter(attr => attr.specified).map(attr => { + this.overlay.setAttribute(attr.name, attr.nodeValue); + }); + } + + on_left_arrow() { + this.update_current_data_point(this.current_index - 1); + } + + on_right_arrow() { + this.update_current_data_point(this.current_index + 1); + } + + set_avg_unit_width_and_x_offset() { + this.avg_unit_width = this.width/(this.x.length + 1); + this.x_offset = this.avg_unit_width; + } +} + +frappe.chart.LineChart = class LineChart extends frappe.chart.AxisChart { + constructor(args) { + super(args); + if(Object.getPrototypeOf(this) !== frappe.chart.LineChart.prototype) { + return; } - return c3.generate(chart_dict); - }, + this.type = 'line-graph'; + + this.setup(); + } + + setup_values() { + super.setup_values(); + this.y_axis_mode = 'span'; + this.x_axis_mode = 'span'; + this.unit_args = { + type: 'dot', + args: { radius: 4 } + }; + } - show_chart: function(show) { - this.opts.wrapper.toggle(show); - }, + make_path(d, color) { + let points_list = d.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y)); + let path_str = "M"+points_list.join("L"); - set_chart_size: function(width, height) { - this.chart.resize({ - width: width, - height: height + d.path = $$.createSVG('path', { + className: `stroke ${color}`, + d: path_str }); - }, - - setup_events: function(){ - var me = this; - var chart_result = me.opts.wrapper.find(".chart_area_result"); - - chart_result.toggle(false); - me.opts.wrapper.find(".show-hide-chart").toggle(true).on("click", function(){ - if ($(this).find(".chart-btn-text").text()==__("Show Chart")) { - chart_result.toggle(true); - $(this).find(".chart-btn-text").text(__("Hide Chart")); + + this.svg_units_group.prepend(d.path); + } +} + +frappe.chart.RegionChart = class RegionChart extends frappe.chart.LineChart { + constructor(args) { + super(args); + + this.type = 'region-graph'; + this.region_fill = 1; + this.setup(); + } +} + +frappe.chart.PercentageChart = class PercentageChart extends frappe.chart.FrappeChart { + constructor(args) { + super(args); + + this.x = this.data.labels; + this.y = this.data.datasets; + + this.get_x_label = this.format_lambdas.x_label; + this.get_y_label = this.format_lambdas.y_label; + this.get_x_tooltip = this.format_lambdas.x_tooltip; + this.get_y_tooltip = this.format_lambdas.y_tooltip; + + this.setup(); + } + + make_chart_area() { + this.chart_wrapper.className += ' ' + 'graph-focus-margin'; + this.chart_wrapper.style.marginTop = '45px'; + + this.stats_wrapper.className += ' ' + 'graph-focus-margin'; + this.stats_wrapper.style.marginBottom = '30px'; + this.stats_wrapper.style.paddingTop = '0px'; + + this.chart_div = $$.create('div', { + className: 'div', + inside: this.chart_wrapper, + width: this.base_width, + height: this.base_height + }); + + this.chart = $$.create('div', { + className: 'progress-chart', + inside: this.chart_div + }); + } + + setup_values() { + this.x.totals = this.x.map((d, i) => { + let total = 0; + this.y.map(e => { + total += e.values[i]; + }); + return total; + }); + + if(!this.x.colors) { + this.x.colors = ['green', 'blue', 'purple', 'red', 'orange', + 'yellow', 'lightblue', 'lightgreen']; + } + } + + setup_utils() { } + setup_components() { + this.percentage_bar = $$.create('div', { + className: 'progress', + inside: this.chart + }); + } + + make_graph_components() { + this.grand_total = this.x.totals.reduce((a, b) => a + b, 0); + this.x.units = []; + this.x.totals.map((total, i) => { + let part = $$.create('div', { + className: `progress-bar background ${this.x.colors[i]}`, + style: `width: ${total*100/this.grand_total}%`, + inside: this.percentage_bar + }); + this.x.units.push(part); + }); + } + + bind_tooltip() { + this.x.units.map((part, i) => { + part.addEventListener('mouseenter', () => { + let g_off = this.chart_wrapper.offset(), p_off = part.offset(); + + let x = p_off.left - g_off.left + part.offsetWidth/2; + let y = p_off.top - g_off.top - 6; + let title = (this.x.formatted && this.x.formatted.length>0 + ? this.x.formatted[i] : this.x[i]) + ': '; + let percent = (this.x.totals[i]*100/this.grand_total).toFixed(1); + + this.tip.set_values(x, y, title, percent); + this.tip.show_tip(); + }); + }); + } + + show_summary() { + let x_values = this.x.formatted && this.x.formatted.length > 0 + ? this.x.formatted : this.x; + this.x.totals.map((d, i) => { + if(d) { + let stats = $$.create('div', { + className: 'stats', + inside: this.stats_wrapper + }); + stats.innerHTML = ` + ${x_values[i]}: + ${d} + `; } - else { - chart_result.toggle(false); - $(this).find(".chart-btn-text").text(__("Show Chart")); + }); + } +} + +frappe.chart.HeatMap = class HeatMap extends frappe.chart.FrappeChart { + constructor({ + start = new Date(moment().subtract(1, 'year').toDate()), + domain = '', + subdomain = '', + data = {}, + discrete_domains = 0, + count_label = '' + }) { + super(arguments[0]); + + this.type = 'heatmap'; + + this.domain = domain; + this.subdomain = subdomain; + this.start = start; + this.data = data; + this.discrete_domains = discrete_domains; + this.count_label = count_label; + + this.legend_colors = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; + + this.translate_x = 0; + this.setup(); + } + + setup_base_values() { + this.today = new Date(); + + if(!this.start) { + this.start = new Date(); + this.start.setFullYear( this.start.getFullYear() - 1 ); + } + this.first_week_start = new Date(this.start.toDateString()); + this.last_week_start = new Date(this.today.toDateString()); + if(this.first_week_start.getDay() !== 7) { + this.add_days(this.first_week_start, (-1) * this.first_week_start.getDay()); + } + if(this.last_week_start.getDay() !== 7) { + this.add_days(this.last_week_start, (-1) * this.last_week_start.getDay()); + } + this.no_of_cols = this.get_weeks_between(this.first_week_start + '', this.last_week_start + '') + 1; + } + + set_width() { + this.base_width = (this.no_of_cols) * 12; + } + + setup_components() { + this.domain_label_group = $$.createSVG("g", { + className: "domain-label-group chart-label", + inside: this.draw_area + }); + this.data_groups = $$.createSVG("g", { + className: "data-groups", + inside: this.draw_area, + transform: `translate(0, 20)` + }); + } + + setup_values() { + this.domain_label_group.textContent = ''; + this.data_groups.textContent = ''; + this.distribution = this.get_distribution(this.data, this.legend_colors); + this.month_names = ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + ]; + + this.render_all_weeks_and_store_x_values(this.no_of_cols); + } + + render_all_weeks_and_store_x_values(no_of_weeks) { + let current_week_sunday = new Date(this.first_week_start); + this.week_col = 0; + this.current_month = current_week_sunday.getMonth(); + + this.months = [this.current_month + '']; + this.month_weeks = {}, this.month_start_points = []; + this.month_weeks[this.current_month] = 0; + this.month_start_points.push(13); + + for(var i = 0; i < no_of_weeks; i++) { + let data_group, month_change = 0; + let day = new Date(current_week_sunday); + + [data_group, month_change] = this.get_week_squares_group(day, this.week_col); + this.data_groups.appendChild(data_group); + this.week_col += 1 + parseInt(this.discrete_domains && month_change); + this.month_weeks[this.current_month]++; + if(month_change) { + this.current_month = (this.current_month + 1) % 12; + this.months.push(this.current_month + ''); + this.month_weeks[this.current_month] = 1; } + this.add_days(current_week_sunday, 7); + } + this.render_month_labels(); + } + + get_week_squares_group(current_date, index) { + const no_of_weekdays = 7; + const square_side = 10; + const cell_padding = 2; + const step = 1; + + let month_change = 0; + let week_col_change = 0; + + let data_group = $$.createSVG("g", { + className: "data-group", + inside: this.data_groups + }); + + for(var y = 0, i = 0; i < no_of_weekdays; i += step, y += (square_side + cell_padding)) { + let data_value = 0; + let color_index = 0; + + // TODO: More foolproof for any data + let timestamp = Math.floor(current_date.getTime()/1000).toFixed(1); + + if(this.data[timestamp]) { + data_value = this.data[timestamp]; + color_index = this.get_max_checkpoint(data_value, this.distribution); + } + + if(this.data[Math.round(timestamp)]) { + data_value = this.data[Math.round(timestamp)]; + color_index = this.get_max_checkpoint(data_value, this.distribution); + } + + let x = 13 + (index + week_col_change) * 12; + + $$.createSVG("rect", { + className: 'day', + inside: data_group, + x: x, + y: y, + width: square_side, + height: square_side, + fill: this.legend_colors[color_index], + 'data-date': this.get_dd_mm_yyyy(current_date), + 'data-value': data_value, + 'data-day': current_date.getDay() + }); + + let next_date = new Date(current_date); + this.add_days(next_date, 1); + if(next_date.getMonth() - current_date.getMonth()) { + month_change = 1; + if(this.discrete_domains) { + week_col_change = 1; + } + + this.month_start_points.push(13 + (index + week_col_change) * 12); + } + current_date = next_date; + } + + return [data_group, month_change]; + } + + render_month_labels() { + // this.first_month_label = 1; + // if (this.first_week_start.getDate() > 8) { + // this.first_month_label = 0; + // } + // this.last_month_label = 1; + + // let first_month = this.months.shift(); + // let first_month_start = this.month_start_points.shift(); + // render first month if + + // let last_month = this.months.pop(); + // let last_month_start = this.month_start_points.pop(); + // render last month if + + this.months.shift(); + this.month_start_points.shift(); + this.months.pop(); + this.month_start_points.pop(); + + this.month_start_points.map((start, i) => { + let month_name = this.month_names[this.months[i]].substring(0, 3); + + $$.createSVG('text', { + className: 'y-value-text', + inside: this.domain_label_group, + x: start + 12, + y: 10, + dy: '.32em', + innerHTML: month_name + }); + + }); + } + + make_graph_components() { + Array.prototype.slice.call( + this.container.querySelectorAll('.graph-stats-container, .sub-title, .title') + ).map(d => { + d.style.display = 'None'; + }); + this.chart_wrapper.style.marginTop = '0px'; + this.chart_wrapper.style.paddingTop = '0px'; + } + + bind_tooltip() { + Array.prototype.slice.call( + document.querySelectorAll(".data-group .day") + ).map(el => { + el.addEventListener('mouseenter', (e) => { + let count = e.target.getAttribute('data-value'); + let date_parts = e.target.getAttribute('data-date').split('-'); + + let month = this.month_names[parseInt(date_parts[1])-1].substring(0, 3); + + let g_off = this.chart_wrapper.getBoundingClientRect(), p_off = e.target.getBoundingClientRect(); + + let width = parseInt(e.target.getAttribute('width')); + let x = p_off.left - g_off.left + (width+2)/2; + let y = p_off.top - g_off.top - (width+2)/2; + let value = count + ' ' + this.count_label; + let name = ' on ' + month + ' ' + date_parts[0] + ', ' + date_parts[2]; + + this.tip.set_values(x, y, name, value, [], 1); + this.tip.show_tip(); + }); + }); + } + + update(data) { + this.data = data; + this.setup_values(); + this.bind_tooltip(); + } + + get_distribution(data={}, mapper_array) { + let data_values = Object.keys(data).map(key => data[key]); + let data_max_value = Math.max(...data_values); + + let distribution_step = 1 / (mapper_array.length - 1); + let distribution = []; + + mapper_array.map((color, i) => { + let checkpoint = data_max_value * (distribution_step * i); + distribution.push(checkpoint); + }); + + return distribution; + } + + get_max_checkpoint(value, distribution) { + return distribution.filter((d, i) => { + if(i === 1) { + return distribution[0] < value; + } + return d <= value; + }).length - 1; + } + + // TODO: date utils, move these out + + // https://stackoverflow.com/a/11252167/6495043 + treat_as_utc(date_str) { + let result = new Date(date_str); + result.setMinutes(result.getMinutes() - result.getTimezoneOffset()); + return result; + } + + get_dd_mm_yyyy(date) { + let dd = date.getDate(); + let mm = date.getMonth() + 1; // getMonth() is zero-based + return [ + (dd>9 ? '' : '0') + dd, + (mm>9 ? '' : '0') + mm, + date.getFullYear() + ].join('-'); + } + + get_weeks_between(start_date_str, end_date_str) { + return Math.ceil(this.get_days_between(start_date_str, end_date_str) / 7); + } + + get_days_between(start_date_str, end_date_str) { + let milliseconds_per_day = 24 * 60 * 60 * 1000; + return (this.treat_as_utc(end_date_str) - this.treat_as_utc(start_date_str)) / milliseconds_per_day; + } + + // mutates + add_days(date, number_of_days) { + date.setDate(date.getDate() + number_of_days); + } + + get_month_name() {} +} + +frappe.chart.SvgTip = class { + constructor({ + parent = null + }) { + this.parent = parent; + this.title_name = ''; + this.title_value = ''; + this.list_values = []; + this.title_value_first = 0; + + this.x = 0; + this.y = 0; + + this.top = 0; + this.left = 0; + + this.setup(); + } + + setup() { + this.make_tooltip(); + } + + refresh() { + this.fill(); + this.calc_position(); + // this.show_tip(); + } + + make_tooltip() { + this.container = $$.create('div', { + className: 'graph-svg-tip comparison', + innerHTML: ` +
      +
      ` + }); + + this.parent.appendChild(this.container); + this.hide_tip(); + + this.title = this.container.querySelector('.title'); + this.data_point_list = this.container.querySelector('.data-point-list'); + + this.parent.addEventListener('mouseleave', () => { + this.hide_tip(); }); } -}); + + fill() { + let title; + if(this.title_value_first) { + title = `${this.title_value}${this.title_name}`; + } else { + title = `${this.title_name}${this.title_value}`; + } + this.title.innerHTML = title; + this.data_point_list.innerHTML = ''; + + this.list_values.map((set) => { + let li = $$.create('li', { + className: `border-top ${set.color || 'black'}`, + innerHTML: `${set.value ? set.value : '' } + ${set.title ? set.title : '' }` + }); + + this.data_point_list.appendChild(li); + }); + } + + calc_position() { + this.top = this.y - this.container.offsetHeight; + this.left = this.x - this.container.offsetWidth/2; + let max_left = this.parent.offsetWidth - this.container.offsetWidth; + + let pointer = this.container.querySelector('.svg-pointer'); + + if(this.left < 0) { + pointer.style.left = `calc(50% - ${-1 * this.left}px)`; + this.left = 0; + } else if(this.left > max_left) { + let delta = this.left - max_left; + pointer.style.left = `calc(50% + ${delta}px)`; + this.left = max_left; + } else { + pointer.style.left = `50%`; + } + } + + set_values(x, y, title_name = '', title_value = '', list_values = [], title_value_first = 0) { + this.title_name = title_name; + this.title_value = title_value; + this.list_values = list_values; + this.x = x; + this.y = y; + this.title_value_first = title_value_first; + this.refresh(); + } + + hide_tip() { + this.container.style.top = '0px'; + this.container.style.left = '0px'; + this.container.style.opacity = '0'; + } + + show_tip() { + this.container.style.top = this.top + 'px'; + this.container.style.left = this.left + 'px'; + this.container.style.opacity = '1'; + } +} + +frappe.chart.map_c3 = (chart) => { + if (chart.data) { + let data = chart.data; + let type = chart.chart_type || 'line'; + if(type === 'pie') { + type = 'percentage'; + } + + let x = {}, y = []; + + if(data.columns) { + let columns = data.columns; + + x = columns.filter(col => { + return col[0] === data.x; + })[0]; + + if(x && x.length) { + let dataset_length = x.length; + let dirty = false; + columns.map(col => { + if(col[0] !== data.x) { + if(col.length === dataset_length) { + let title = col[0]; + col.splice(0, 1); + y.push({ + title: title, + values: col, + }); + } else { + dirty = true; + } + } + }); + + if(dirty) { + return; + } + + x.splice(0, 1); + + return { + type: type, + y: y, + x: x + } + + } + } else if(data.rows) { + let rows = data.rows; + x = rows[0]; + + rows.map((row, i) => { + if(i === 0) { + x = row; + } else { + y.push({ + title: 'data' + i, + values: row, + }); + } + }); + + return { + type: type, + y: y, + x: x + } + } + } +} + +// Helpers +frappe.chart.utils = {}; +frappe.chart.utils.float_2 = d => parseFloat(d.toFixed(2)); +function $$(expr, con) { + return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; +} + +// $$.findNodeIndex = (node) => +// { +// var i = 0; +// while (node = node.previousSibling) { +// if (node.nodeType === 1) { ++i; } +// } +// return i; +// } + +$$.create = function(tag, o) { + var element = document.createElement(tag); + + for (var i in o) { + var val = o[i]; + + if (i === "inside") { + $$(val).appendChild(element); + } + else if (i === "around") { + var ref = $$(val); + ref.parentNode.insertBefore(element, ref); + element.appendChild(ref); + } + else if (i in element) { + element[i] = val; + } + else { + element.setAttribute(i, val); + } + } + + return element; +}; + +$$.createSVG = function(tag, o) { + var element = document.createElementNS("http://www.w3.org/2000/svg", tag); + + for (var i in o) { + var val = o[i]; + + if (i === "inside") { + $$(val).appendChild(element); + } + else if (i === "around") { + var ref = $$(val); + ref.parentNode.insertBefore(element, ref); + element.appendChild(ref); + } + else { + if(i === "className") { i = "class"; } + if(i === "innerHTML") { + element['textContent'] = val; + } else { + element.setAttribute(i, val); + } + } + } + + return element; +}; + +$$.runSVGAnimation = (svg_container, elements) => { + let parent = elements[0][0]['unit'].parentNode; + + let new_elements = []; + let anim_elements = []; + + elements.map(element => { + let obj = element[0]; + // let index = $$.findNodeIndex(obj.unit); + + let anim_element, new_element; + + element[0] = obj.unit; + [anim_element, new_element] = $$.animateSVG(...element); + + new_elements.push(new_element); + anim_elements.push(anim_element); + + parent.replaceChild(anim_element, obj.unit); + + if(obj.array) { + obj.array[obj.index] = new_element; + } else { + obj.object[obj.key] = new_element; + } + }); + + let anim_svg = svg_container.cloneNode(true); + + anim_elements.map((anim_element, i) => { + parent.replaceChild(new_elements[i], anim_element); + elements[i][0] = new_elements[i]; + }); + + return anim_svg; +} + +$$.animateSVG = (element, props, dur, easing_type="linear") => { + let easing = { + ease: "0.25 0.1 0.25 1", + linear: "0 0 1 1", + // easein: "0.42 0 1 1", + easein: "0.1 0.8 0.2 1", + easeout: "0 0 0.58 1", + easeinout: "0.42 0 0.58 1" + } + + let anim_element = element.cloneNode(false); + let new_element = element.cloneNode(false); + + for(var attributeName in props) { + let animate_element = document.createElementNS("http://www.w3.org/2000/svg", "animate"); + + let current_value = element.getAttribute(attributeName); + let value = props[attributeName]; + + let anim_attr = { + attributeName: attributeName, + from: current_value, + to: value, + begin: "0s", + dur: dur/1000 + "s", + values: current_value + ";" + value, + keySplines: easing[easing_type], + keyTimes: "0;1", + calcMode: "spline" + } + + for (var i in anim_attr) { + animate_element.setAttribute(i, anim_attr[i]); + } + + anim_element.appendChild(animate_element); + new_element.setAttribute(attributeName, value); + } + + return [anim_element, new_element]; +} + +$$.bind = function(element, o) { + if (element) { + for (var event in o) { + var callback = o[event]; + + event.split(/\s+/).forEach(function (event) { + element.addEventListener(event, callback); + }); + } + } +}; + +$$.unbind = function(element, o) { + if (element) { + for (var event in o) { + var callback = o[event]; + + event.split(/\s+/).forEach(function(event) { + element.removeEventListener(event, callback); + }); + } + } +}; + +$$.fire = function(target, type, properties) { + var evt = document.createEvent("HTMLEvents"); + + evt.initEvent(type, true, true ); + + for (var j in properties) { + evt[j] = properties[j]; + } + + return target.dispatchEvent(evt); +}; diff --git a/frappe/public/js/frappe/ui/graphs.js b/frappe/public/js/frappe/ui/graphs.js deleted file mode 100644 index 52f2b07ad2..0000000000 --- a/frappe/public/js/frappe/ui/graphs.js +++ /dev/null @@ -1,1089 +0,0 @@ -// specific_values = [ -// { -// name: "Average", -// line_type: "dashed", // "dashed" or "solid" -// value: 10 -// }, - -// summary = [ -// { -// name: "Total", -// color: 'blue', // Indicator colors: 'grey', 'blue', 'red', 'green', 'orange', -// // 'purple', 'darkgrey', 'black', 'yellow', 'lightblue' -// value: 80 -// } -// ] - -// Graph: Abstract object -frappe.ui.Graph = class Graph { - constructor({ - parent = null, - height = 240, - - title = '', subtitle = '', - - y = [], - x = [], - - specific_values = [], - summary = [], - - mode = '' - }) { - - if(Object.getPrototypeOf(this) === frappe.ui.Graph.prototype) { - if(mode === 'line') { - return new frappe.ui.LineGraph(arguments[0]); - } else if(mode === 'bar') { - return new frappe.ui.BarGraph(arguments[0]); - } else if(mode === 'percentage') { - return new frappe.ui.PercentageGraph(arguments[0]); - } - } - - this.parent = parent; - - this.set_margins(height); - - this.title = title; - this.subtitle = subtitle; - - // Begin axis graph-related args - - this.y = y; - this.x = x; - - this.specific_values = specific_values; - this.summary = summary; - - this.mode = mode; - - // this.current_hover_index = 0; - // this.current_selected_index = 0; - - this.$graph = null; - - // Validate all arguments, check passed data format, set defaults - - } - - setup() { - frappe.require("assets/frappe/js/lib/snap.svg-min.js", () => { - this.bind_window_event(); - this.refresh(); - }); - } - - bind_window_event() { - $(window).on('resize orientationChange', () => { - this.refresh(); - }); - } - - refresh() { - - this.setup_base_values(); - this.set_width(); - this.width = this.base_width - this.translate_x * 2; - - this.setup_container(); - this.setup_components(); - this.setup_values(); - this.setup_utils(); - - this.make_graph_components(); - this.make_tooltip(); - - if(this.summary.length > 0) { - this.show_custom_summary(); - } else { - this.show_summary(); - } - } - - set_margins(height) { - this.base_height = height; - this.height = height - 40; - - this.translate_x = 60; - this.translate_y = 10; - } - - set_width() { - this.base_width = this.parent.width(); - } - - setup_base_values() {} - - setup_container() { - // Graph needs a dedicated parent element - this.parent.empty(); - - this.container = $('
      ') - .addClass('graph-container') - .append($(`
      ${this.title}
      `)) - .append($(`
      ${this.subtitle}
      `)) - .append($(`
      `)) - .append($(`
      `)) - .appendTo(this.parent); - - this.$graphics = this.container.find('.graphics'); - this.$stats_container = this.container.find('.graph-stats-container'); - - this.$graph = $('
      ') - .addClass(this.mode + '-graph') - .appendTo(this.$graphics); - - this.$graph.append(this.make_graph_area()); - } - - make_graph_area() { - this.$svg = $(``); - this.snap = new Snap(this.$svg[0]); - return this.$svg; - } - - setup_components() { - this.y_axis_group = this.snap.g().attr({ class: "y axis" }); - this.x_axis_group = this.snap.g().attr({ class: "x axis" }); - this.data_units = this.snap.g().attr({ class: "data-points" }); - this.specific_y_lines = this.snap.g().attr({ class: "specific axis" }); - } - - setup_values() { - // Multiplier - let all_values = this.specific_values.map(d => d.value); - this.y.map(d => { - all_values = all_values.concat(d.values); - }); - [this.upper_limit, this.parts] = this.get_upper_limit_and_parts(all_values); - this.multiplier = this.height / this.upper_limit; - - // Baselines - this.set_avg_unit_width_and_x_offset(); - - this.x_axis_values = this.x.values.map((d, i) => this.x_offset + i * this.avg_unit_width); - this.y_axis_values = this.get_y_axis_values(this.upper_limit, this.parts); - - // Data points - this.y.map(d => { - d.y_tops = d.values.map( val => this.height - val * this.multiplier ); - d.data_units = []; - }); - - this.calc_min_tops(); - } - - set_avg_unit_width_and_x_offset() { - this.avg_unit_width = this.width/(this.x.values.length - 1); - this.x_offset = 0; - } - - calc_min_tops() { - this.y_min_tops = new Array(this.x_axis_values.length).fill(9999); - this.y.map(d => { - d.y_tops.map( (y_top, i) => { - if(y_top < this.y_min_tops[i]) { - this.y_min_tops[i] = y_top; - } - }); - }); - } - - make_graph_components() { - this.make_y_axis(); - this.make_x_axis(); - this.y_colors = ['lightblue', 'purple', 'blue', 'green', 'lightgreen', - 'yellow', 'orange', 'red'] - - this.y.map((d, i) => { - this.make_units(d.y_tops, d.color || this.y_colors[i], i); - this.make_path(d, d.color || this.y_colors[i]); - }); - - if(this.specific_values.length > 0) { - this.show_specific_values(); - } - this.setup_group(); - } - - setup_group() { - this.snap.g( - this.y_axis_group, - this.x_axis_group, - this.data_units, - this.specific_y_lines - ).attr({ - transform: `translate(${this.translate_x}, ${this.translate_y})` - }); - } - - // make HORIZONTAL lines for y values - make_y_axis() { - let width, text_end_at = -9, label_class = '', start_at = 0; - if(this.y_axis_mode === 'span') { // long spanning lines - width = this.width + 6; - start_at = -6; - } else if(this.y_axis_mode === 'tick'){ // short label lines - width = -6; - label_class = 'y-axis-label'; - } - - this.y_axis_values.map((point) => { - this.y_axis_group.add(this.snap.g( - this.snap.line(start_at, 0, width, 0), - this.snap.text(text_end_at, 0, point+"").attr({ - dy: ".32em", - class: "y-value-text" - }) - ).attr({ - class: `tick ${label_class}`, - transform: `translate(0, ${this.height - point * this.multiplier })` - })); - }); - } - - // make VERTICAL lines for x values - make_x_axis() { - let start_at, height, text_start_at, label_class = ''; - if(this.x_axis_mode === 'span') { // long spanning lines - start_at = -7; - height = this.height + 15; - text_start_at = this.height + 25; - } else if(this.x_axis_mode === 'tick'){ // short label lines - start_at = this.height; - height = 6; - text_start_at = 9; - label_class = 'x-axis-label'; - } - - this.x_axis_group.attr({ - transform: `translate(0,${start_at})` - }); - this.x.values.map((point, i) => { - let allowed_space = this.avg_unit_width * 1.5; - if(this.get_strwidth(point) > allowed_space) { - let allowed_letters = allowed_space / 8; - point = point.slice(0, allowed_letters-3) + " ..."; - } - this.x_axis_group.add(this.snap.g( - this.snap.line(0, 0, 0, height), - this.snap.text(0, text_start_at, point).attr({ - dy: ".71em", - class: "x-value-text" - }) - ).attr({ - class: `tick ${label_class}`, - transform: `translate(${ this.x_axis_values[i] }, 0)` - })); - }); - } - - make_units(y_values, color, dataset_index) { - let d = this.unit_args; - y_values.map((y, i) => { - let data_unit = this.draw[d.type]( - this.x_axis_values[i], - y, - d.args, - color, - dataset_index - ); - this.data_units.add(data_unit); - this.y[dataset_index].data_units.push(data_unit); - }); - } - - make_path() { } - - make_tooltip() { - // should be w.r.t. this.parent - this.tip = new frappe.ui.SvgTip({ - parent: this.$graphics, - }); - this.bind_tooltip(); - } - - bind_tooltip() { - // should be w.r.t. this.parent, but will have to take care of - // all the elements and padding, margins on top - this.$graphics.on('mousemove', (e) => { - let offset = this.$graphics.offset(); - var relX = e.pageX - offset.left - this.translate_x; - var relY = e.pageY - offset.top - this.translate_y; - - if(relY < this.height + this.translate_y * 2) { - this.map_tooltip_x_position_and_show(relX); - } else { - this.tip.hide_tip() - } - }); - } - - map_tooltip_x_position_and_show(relX) { - for(var i=this.x_axis_values.length - 1; i >= 0 ; i--) { - let x_val = this.x_axis_values[i]; - // let delta = i === 0 ? this.avg_unit_width : x_val - this.x_axis_values[i-1]; - if(relX > x_val - this.avg_unit_width/2) { - let x = x_val + this.translate_x - 0.5; - let y = this.y_min_tops[i] + this.translate_y; - let title = this.x.formatted && this.x.formatted.length>0 - ? this.x.formatted[i] : this.x.values[i]; - let values = this.y.map((set, j) => { - return { - title: set.title, - value: set.formatted ? set.formatted[i] : set.values[i], - color: set.color || this.y_colors[j], - } - }); - - this.tip.set_values(x, y, title, '', values); - this.tip.show_tip(); - break; - } - } - } - - show_specific_values() { - this.specific_values.map(d => { - this.specific_y_lines.add(this.snap.g( - this.snap.line(0, 0, this.width, 0).attr({ - class: d.line_type === "dashed" ? "dashed": "" - }), - this.snap.text(this.width + 5, 0, d.name.toUpperCase()).attr({ - dy: ".32em", - class: "specific-value", - }) - ).attr({ - class: "tick", - transform: `translate(0, ${this.height - d.value * this.multiplier })` - })); - }); - } - - show_summary() { } - - show_custom_summary() { - this.summary.map(d => { - this.$stats_container.append($(`
      - ${d.name}: ${d.value} -
      `)); - }); - } - - change_values(new_y) { - let u = this.unit_args; - this.y.map((d, i) => { - let new_d = new_y[i]; - new_d.y_tops = new_d.values.map(val => this.height - val * this.multiplier); - - // below is equal to this.y[i].data_units.. - d.data_units.map((unit, j) => { - let current_y_top = d.y_tops[j]; - let current_height = this.height - current_y_top; - - let new_y_top = new_d.y_tops[j]; - let new_height = current_height - (new_y_top - current_y_top); - - this.animate[u.type](unit, new_y_top, {new_height: new_height}); - }); - }); - - // Replace values and formatted and tops - this.y.map((d, i) => { - let new_d = new_y[i]; - [d.values, d.formatted, d.y_tops] = [new_d.values, new_d.formatted, new_d.y_tops]; - }); - - this.calc_min_tops(); - - // create new x,y pair string and animate path - if(this.y[0].path) { - new_y.map((e, i) => { - let new_points_list = e.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y)); - let new_path_str = "M"+new_points_list.join("L"); - this.y[i].path.animate({d:new_path_str}, 300, mina.easein); - }); - } - } - - // Helpers - get_strwidth(string) { - return string.length * 8; - } - - get_upper_limit_and_parts(array) { - let max_val = parseInt(Math.max(...array)); - if((max_val+"").length <= 1) { - return [10, 5]; - } else { - let multiplier = Math.pow(10, ((max_val+"").length - 1)); - let significant = Math.ceil(max_val/multiplier); - if(significant % 2 !== 0) significant++; - let parts = (significant < 5) ? significant : significant/2; - return [significant * multiplier, parts]; - } - } - - get_y_axis_values(upper_limit, parts) { - let y_axis = []; - for(var i = 0; i <= parts; i++){ - y_axis.push(upper_limit / parts * i); - } - return y_axis; - } - - // Objects - setup_utils() { - this.draw = { - 'bar': (x, y, args, color, index) => { - let total_width = this.avg_unit_width - args.space_width; - let start_x = x - total_width/2; - - let width = total_width / args.no_of_datasets; - let current_x = start_x + width * index; - if(y == this.height) { - y = this.height * 0.98; - } - return this.snap.rect(current_x, y, width, this.height - y).attr({ - class: `bar mini fill ${color}` - }); - }, - 'dot': (x, y, args, color) => { - return this.snap.circle(x, y, args.radius).attr({ - class: `fill ${color}` - }); - }, - 'rect': (x, y, args, color) => { - return this.snap.rect(x, y, args.width, args.height).attr({ - class: `fill ${color}` - }); - } - }; - - this.animate = { - 'bar': (bar, new_y, args) => { - bar.animate({height: args.new_height, y: new_y}, 300, mina.easein); - }, - 'dot': (dot, new_y) => { - dot.animate({cy: new_y}, 300, mina.easein); - } - }; - } -}; - -frappe.ui.BarGraph = class BarGraph extends frappe.ui.Graph { - constructor(args = {}) { - super(args); - this.setup(); - } - - setup_values() { - var me = this; - super.setup_values(); - this.x_offset = this.avg_unit_width; - this.y_axis_mode = 'span'; - this.x_axis_mode = 'tick'; - this.unit_args = { - type: 'bar', - args: { - // More intelligent width setting - space_width:me.avg_unit_width/2, - no_of_datasets: this.y.length - } - }; - } - - set_avg_unit_width_and_x_offset() { - this.avg_unit_width = this.width/(this.x.values.length + 1); - this.x_offset = this.avg_unit_width; - } -}; - -frappe.ui.LineGraph = class LineGraph extends frappe.ui.Graph { - constructor(args = {}) { - super(args); - this.setup(); - } - - setup_values() { - super.setup_values(); - this.y_axis_mode = 'tick'; - this.x_axis_mode = 'span'; - this.unit_args = { - type: 'dot', - args: { radius: 4 } - }; - } - - make_path(d, color) { - let points_list = d.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y)); - let path_str = "M"+points_list.join("L"); - d.path = this.snap.path(path_str).attr({class: `stroke ${color}`}); - this.data_units.prepend(d.path); - } -}; - -frappe.ui.PercentageGraph = class PercentageGraph extends frappe.ui.Graph { - constructor(args = {}) { - super(args); - this.setup(); - } - - make_graph_area() { - this.$graphics.addClass('graph-focus-margin').attr({ - style: `margin-top: 45px;` - }); - this.$stats_container.addClass('graph-focus-margin').attr({ - style: `padding-top: 0px; margin-bottom: 30px;` - }); - this.$div = $(`
      -
      -
      `); - this.$chart = this.$div.find('.progress-chart'); - return this.$div; - } - - setup_values() { - this.x.totals = this.x.values.map((d, i) => { - let total = 0; - this.y.map(e => { - total += e.values[i]; - }); - return total; - }); - - if(!this.x.colors) { - this.x.colors = ['green', 'blue', 'purple', 'red', 'orange', - 'yellow', 'lightblue', 'lightgreen']; - } - } - - setup_utils() { } - setup_components() { - this.$percentage_bar = $(`
      -
      `).appendTo(this.$chart); // get this.height, width and avg from this if needed - } - - make_graph_components() { - this.grand_total = this.x.totals.reduce((a, b) => a + b, 0); - this.x.units = []; - this.x.totals.map((total, i) => { - let $part = $(`
      `); - this.x.units.push($part); - this.$percentage_bar.append($part); - }); - } - - bind_tooltip() { - this.x.units.map(($part, i) => { - $part.on('mouseenter', () => { - let g_off = this.$graphics.offset(), p_off = $part.offset(); - - let x = p_off.left - g_off.left + $part.width()/2; - let y = p_off.top - g_off.top - 6; - let title = (this.x.formatted && this.x.formatted.length>0 - ? this.x.formatted[i] : this.x.values[i]) + ': '; - let percent = (this.x.totals[i]*100/this.grand_total).toFixed(1); - - this.tip.set_values(x, y, title, percent); - this.tip.show_tip(); - }); - }); - } - - show_summary() { - let x_values = this.x.formatted && this.x.formatted.length > 0 - ? this.x.formatted : this.x.values; - this.x.totals.map((d, i) => { - if(d) { - this.$stats_container.append($(`
      - - ${x_values[i]}: - ${d} - -
      `)); - } - }); - } -}; - -frappe.ui.HeatMap = class HeatMap extends frappe.ui.Graph { - constructor({ - parent = null, - height = 240, - title = '', subtitle = '', - - start = new Date(moment().subtract(1, 'year').toDate()), - domain = '', - subdomain = '', - data = {}, - discrete_domains = 0, - count_label = '', - - // TODO: remove these graph related args - y = [], - x = [], - specific_values = [], - summary = [], - mode = 'heatmap' - } = {}) { - super(arguments[0]); - this.start = start; - this.data = data; - this.discrete_domains = discrete_domains; - - this.count_label = count_label; - - - this.legend_colors = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; - this.setup(); - } - - setup_base_values() { - this.today = new Date(); - - if(!this.start) { - this.start = new Date(); - this.start.setFullYear( this.start.getFullYear() - 1 ); - } - this.first_week_start = new Date(this.start.toDateString()); - this.last_week_start = new Date(this.today.toDateString()); - if(this.first_week_start.getDay() !== 7) { - this.add_days(this.first_week_start, (-1) * this.first_week_start.getDay()); - } - if(this.last_week_start.getDay() !== 7) { - this.add_days(this.last_week_start, (-1) * this.last_week_start.getDay()); - } - this.no_of_cols = this.get_weeks_between(this.first_week_start + '', this.last_week_start + '') + 1; - } - - set_width() { - this.base_width = (this.no_of_cols) * 12; - } - - setup_components() { - this.domain_label_group = this.snap.g().attr({ class: "domain-label-group chart-label" }); - this.data_groups = this.snap.g().attr({ class: "data-groups", transform: `translate(0, 20)` }); - } - - setup_values() { - this.distribution = this.get_distribution(this.data, this.legend_colors); - this.month_names = ["January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" - ]; - - this.render_all_weeks_and_store_x_values(this.no_of_cols); - } - - render_all_weeks_and_store_x_values(no_of_weeks) { - let current_week_sunday = new Date(this.first_week_start); - this.week_col = 0; - this.current_month = current_week_sunday.getMonth(); - - this.months = [this.current_month + '']; - this.month_weeks = {}, this.month_start_points = []; - this.month_weeks[this.current_month] = 0; - this.month_start_points.push(13); - - this.date_values = {}; - - Object.keys(this.data).map(key => { - let date = new Date(key * 1000); - let date_str = this.get_dd_mm_yyyy(date); - this.date_values[date_str] = this.data[key]; - }); - - for(var i = 0; i < no_of_weeks; i++) { - let data_group, month_change = 0; - let day = new Date(current_week_sunday); - - [data_group, month_change] = this.get_week_squares_group(day, this.week_col); - this.data_groups.add(data_group); - this.week_col += 1 + parseInt(this.discrete_domains && month_change); - this.month_weeks[this.current_month]++; - if(month_change) { - this.current_month = (this.current_month + 1) % 12; - this.months.push(this.current_month + ''); - this.month_weeks[this.current_month] = 1; - } - this.add_days(current_week_sunday, 7); - } - this.render_month_labels(); - } - - get_week_squares_group(current_date, index) { - const no_of_weekdays = 7; - const square_side = 10; - const cell_padding = 2; - const step = 1; - - let month_change = 0; - let week_col_change = 0; - - let data_group = this.snap.g().attr({ class: "data-group" }); - - for(var y = 0, i = 0; i < no_of_weekdays; i += step, y += (square_side + cell_padding)) { - let data_value = 0; - let color_index = 0; - - let timestamp = this.get_dd_mm_yyyy(current_date); - - if(this.date_values[timestamp]) { - data_value = this.date_values[timestamp]; - color_index = this.get_max_checkpoint(data_value, this.distribution); - } - - if(this.date_values[Math.round(timestamp)]) { - data_value = this.date_values[Math.round(timestamp)]; - color_index = this.get_max_checkpoint(data_value, this.distribution); - } - - let x = 13 + (index + week_col_change) * 12; - - data_group.add(this.snap.rect(x, y, square_side, square_side).attr({ - 'class': `day`, - 'fill': this.legend_colors[color_index], - 'data-date': this.get_dd_mm_yyyy(current_date), - 'data-value': data_value, - 'data-day': current_date.getDay() - })); - - let next_date = new Date(current_date); - this.add_days(next_date, 1); - if(next_date.getMonth() - current_date.getMonth()) { - month_change = 1; - if(this.discrete_domains) { - week_col_change = 1; - } - - this.month_start_points.push(13 + (index + week_col_change) * 12); - } - current_date = next_date; - } - - return [data_group, month_change]; - } - - render_month_labels() { - this.first_month_label = 1; - // if (this.first_week_start.getDate() > 8) { - // this.first_month_label = 0; - // } - this.last_month_label = 1; - - let first_month = this.months.shift(); - let first_month_start = this.month_start_points.shift(); - // render first month if - - let last_month = this.months.pop(); - let last_month_start = this.month_start_points.pop(); - // render last month if - - this.month_start_points.map((start, i) => { - let month_name = this.month_names[this.months[i]].substring(0, 3); - this.domain_label_group.add(this.snap.text(start + 12, 10, month_name).attr({ - dy: ".32em", - class: "y-value-text" - })); - }); - } - - make_graph_components() { - this.container.find('.graph-stats-container, .sub-title, .title').hide(); - this.container.find('.graphics').css({'margin-top': '0px', 'padding-top': '0px'}); - } - - bind_tooltip() { - this.container.on('mouseenter', '.day', (e) => { - let subdomain = $(e.target); - let count = subdomain.attr('data-value'); - let date_parts = subdomain.attr('data-date').split('-'); - - let month = this.month_names[parseInt(date_parts[1])-1].substring(0, 3); - - let g_off = this.$graphics.offset(), p_off = subdomain.offset(); - - let width = parseInt(subdomain.attr('width')); - let x = p_off.left - g_off.left + (width+2)/2; - let y = p_off.top - g_off.top - (width+2)/2; - let value = count + ' ' + this.count_label; - let name = ' on ' + month + ' ' + date_parts[0] + ', ' + date_parts[2]; - - this.tip.set_values(x, y, name, value, [], 1); - this.tip.show_tip(); - }); - } - - update(data) { - this.data = data; - this.setup_values(); - } - - get_distribution(data={}, mapper_array) { - let data_values = Object.keys(data).map(key => data[key]); - let data_max_value = Math.max(...data_values); - - let distribution_step = 1 / (mapper_array.length - 1); - let distribution = []; - - mapper_array.map((color, i) => { - let checkpoint = data_max_value * (distribution_step * i); - distribution.push(checkpoint); - }); - - return distribution; - } - - get_max_checkpoint(value, distribution) { - return distribution.filter((d, i) => { - return value > d; - }).length; - } - - // TODO: date utils, move these out - - // https://stackoverflow.com/a/11252167/6495043 - treat_as_utc(date_str) { - let result = new Date(date_str); - result.setMinutes(result.getMinutes() - result.getTimezoneOffset()); - return result; - } - - get_dd_mm_yyyy(date) { - let dd = date.getDate(); - let mm = date.getMonth() + 1; // getMonth() is zero-based - return [ - (dd>9 ? '' : '0') + dd, - (mm>9 ? '' : '0') + mm, - date.getFullYear() - ].join('-'); - } - - get_weeks_between(start_date_str, end_date_str) { - return Math.ceil(this.get_days_between(start_date_str, end_date_str) / 7); - } - - get_days_between(start_date_str, end_date_str) { - let milliseconds_per_day = 24 * 60 * 60 * 1000; - return (this.treat_as_utc(end_date_str) - this.treat_as_utc(start_date_str)) / milliseconds_per_day; - } - - // mutates - add_days(date, number_of_days) { - date.setDate(date.getDate() + number_of_days); - } - - get_month_name() {} -} - -frappe.ui.SvgTip = class { - constructor({ - parent = null - }) { - this.parent = parent; - this.title_name = ''; - this.title_value = ''; - this.list_values = []; - this.title_value_first = 0; - - this.x = 0; - this.y = 0; - - this.top = 0; - this.left = 0; - - this.setup(); - } - - setup() { - this.make_tooltip(); - } - - refresh() { - this.fill(); - this.calc_position(); - // this.show_tip(); - } - - make_tooltip() { - this.container = $(`
      - -
        -
        -
        `).appendTo(this.parent); - this.hide_tip(); - - this.title = this.container.find('.title'); - this.data_point_list = this.container.find('.data-point-list'); - - this.parent.on('mouseleave', () => { - this.hide_tip(); - }); - } - - fill() { - let title; - if(this.title_value_first) { - title = `${this.title_value}${this.title_name}`; - } else { - title = `${this.title_name}${this.title_value}`; - } - this.title.html(title); - this.data_point_list.empty(); - this.list_values.map((set, i) => { - let $li = $(`
      • - ${set.value ? set.value : '' } - ${set.title ? set.title : '' } -
      • `).addClass(`border-top ${set.color || 'black'}`); - - this.data_point_list.append($li); - }); - } - - calc_position() { - this.top = this.y - this.container.height(); - this.left = this.x - this.container.width()/2; - let max_left = this.parent.width() - this.container.width(); - - let $pointer = this.container.find('.svg-pointer'); - - if(this.left < 0) { - $pointer.css({ 'left': `calc(50% - ${-1 * this.left}px)` }); - this.left = 0; - } else if(this.left > max_left) { - let delta = this.left - max_left; - $pointer.css({ 'left': `calc(50% + ${delta}px)` }); - this.left = max_left; - } else { - $pointer.css({ 'left': `50%` }); - } - } - - set_values(x, y, title_name = '', title_value = '', list_values = [], title_value_first = 0) { - this.title_name = title_name; - this.title_value = title_value; - this.list_values = list_values; - this.x = x; - this.y = y; - this.title_value_first = title_value_first; - this.refresh(); - } - - hide_tip() { - this.container.css({ - 'top': '0px', - 'left': '0px', - 'opacity': '0' - }); - } - - show_tip() { - this.container.css({ - 'top': this.top + 'px', - 'left': this.left + 'px', - 'opacity': '1' - }); - } -}; - - -frappe.provide("frappe.ui.graphs"); - -frappe.ui.graphs.get_timeseries = function(start, frequency, length) { - -} - -frappe.ui.graphs.map_c3 = function(chart) { - if (chart.data) { - let data = chart.data; - let mode = chart.chart_type || 'line'; - if(mode === 'pie') { - mode = 'percentage'; - } - - let x = {}, y = []; - - if(data.columns) { - let columns = data.columns; - - x.values = columns.filter(col => { - return col[0] === data.x; - })[0]; - - if(x.values && x.values.length) { - let dataset_length = x.values.length; - let dirty = false; - columns.map(col => { - if(col[0] !== data.x) { - if(col.length === dataset_length) { - let title = col[0]; - col.splice(0, 1); - y.push({ - title: title, - values: col, - }); - } else { - dirty = true; - } - } - }) - - if(dirty) { - return; - } - - x.values.splice(0, 1); - - return { - mode: mode, - y: y, - x: x - } - - } - } else if(data.rows) { - let rows = data.rows; - x.values = rows[0]; - - rows.map((row, i) => { - if(i === 0) { - x.values = row; - } else { - y.push({ - title: 'data' + i, - values: row, - }) - } - }); - - return { - mode: mode, - y: y, - x: x - } - } - } -} - - -// frappe.ui.CompositeGraph = class { -// constructor({ -// parent = null -// }) { -// this.parent = parent; -// this.title_name = ''; -// this.title_value = ''; -// this.list_values = []; - -// this.x = 0; -// this.y = 0; - -// this.top = 0; -// this.left = 0; - -// this.setup(); -// } -// } diff --git a/frappe/public/js/frappe/ui/page.js b/frappe/public/js/frappe/ui/page.js index 659d007e6a..5fde62d898 100644 --- a/frappe/public/js/frappe/ui/page.js +++ b/frappe/public/js/frappe/ui/page.js @@ -9,7 +9,6 @@ * @param {string} opts.parent [HTMLElement] Parent element * @param {boolean} opts.single_column Whether to include sidebar * @param {string} [opts.title] Page title - * @param {Object} [opts.required_libs] resources to load * @param {Object} [opts.make_page] * * @returns {frappe.ui.Page} @@ -42,10 +41,10 @@ frappe.ui.Page = Class.extend({ make: function() { this.wrapper = $(this.parent); - this.setup_render(); + this.add_main_section(); }, - get_empty_state: function({title, message, primary_action}) { + get_empty_state: function(title, message, primary_action) { let $empty_state = $(`
        @@ -53,30 +52,15 @@ frappe.ui.Page = Class.extend({ ${title}

        ${message}

        - +
        + +
        `); - $empty_state.find('.btn-primary').on('click', () => { - primary_action.on_click(); - }); - return $empty_state; }, - setup_render: function() { - var lib_exists = (typeof this.required_libs === 'string' && this.required_libs) - || ($.isArray(this.required_libs) && this.required_libs.length); - - if (lib_exists) { - this.load_lib(() => { - this.add_main_section(); - }); - } else { - this.add_main_section(); - } - }, - load_lib: function (callback) { frappe.require(this.required_libs, callback); }, @@ -511,14 +495,4 @@ frappe.ui.Page = Class.extend({ this.wrapper.trigger('view-change'); }, -}); - -frappe.ui.scroll = function(element, animate, additional_offset) { - var header_offset = $(".navbar").height() + $(".page-head").height(); - var top = $(element).offset().top - header_offset - cint(additional_offset); - if (animate) { - $("html, body").animate({ scrollTop: top }); - } else { - $(window).scrollTop(top); - } -} +}); \ No newline at end of file diff --git a/frappe/public/js/frappe/ui/sidebar.js b/frappe/public/js/frappe/ui/sidebar.js new file mode 100644 index 0000000000..b3fe8f5b56 --- /dev/null +++ b/frappe/public/js/frappe/ui/sidebar.js @@ -0,0 +1,56 @@ +frappe.provide('frappe.ui'); + +frappe.ui.Sidebar = class Sidebar { + constructor({ wrapper, css_class }) { + this.wrapper = wrapper; + this.css_class = css_class; + this.make_dom(); + } + + make_dom() { + this.wrapper.html(` + + `); + + this.$sidebar = this.wrapper.find('.' + this.css_class); + } + + add_item(item, section) { + let $section; + if(!section && this.wrapper.find('.sidebar-menu').length === 0) { + // if no section, add section with no heading + $section = this.get_section(); + } else { + $section = this.get_section(section); + } + + const $li_item = $(` +
      • ${item.label}
      • + `).click( + () => item.on_click && item.on_click() + ); + + $section.append($li_item); + } + + get_section(section_heading="") { + let $section = $(this.wrapper.find( + `[data-section-heading="${section_heading}"]`)); + if($section.length) { + return $section; + } + + const $section_heading = section_heading ? + `
      • ${section_heading}
      • ` : ''; + + $section = $(` + + `); + + this.$sidebar.append($section); + return $section; + } +}; diff --git a/frappe/public/js/frappe/ui/toolbar/search_utils.js b/frappe/public/js/frappe/ui/toolbar/search_utils.js index f355e8547b..c434bef88f 100644 --- a/frappe/public/js/frappe/ui/toolbar/search_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/search_utils.js @@ -50,24 +50,27 @@ frappe.search.utils = { find(values, keywords, function(match) { var out = { route: match[1] - } - if(match[1][0]==='Form' && match[1][2]) { - if(match[1][1] !== match[1][2]) { + }; + if (match[1][0]==='Form') { + if (match[1].length > 2 && match[1][1] !== match[1][2]) { out.label = __(match[1][1]) + " " + match[1][2].bold(); out.value = __(match[1][1]) + " " + match[1][2]; } else { out.label = __(match[1][1]).bold(); out.value = __(match[1][1]); } - } else if(in_list(['List', 'Report', 'Tree', 'modules', 'query-report'], match[1][0])) { + } else if (in_list(['List', 'Report', 'Tree', 'modules', 'query-report'], match[1][0]) && (match[1].length > 1)) { var type = match[1][0], label = type; if(type==='modules') label = 'Module'; else if(type==='query-report') label = 'Report'; out.label = __(match[1][1]).bold() + " " + __(label); out.value = __(match[1][1]) + " " + __(label); - } else { + } else if (match[0]) { out.label = match[0].bold(); out.value = match[0]; + } else { + // eslint-disable-next-line + console.log('Illegal match', match); } out.index = 80; return out; diff --git a/frappe/public/js/frappe/views/kanban/kanban_board.js b/frappe/public/js/frappe/views/kanban/kanban_board.js index 7b33067f19..a236d6a4fe 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_board.js +++ b/frappe/public/js/frappe/views/kanban/kanban_board.js @@ -593,7 +593,7 @@ frappe.provide("frappe.views"); function make_dom() { var opts = { name: card.name, - title: card.title + title: remove_img_tags(card.title) }; self.$card = $(frappe.render_template('kanban_card', opts)) .appendTo(wrapper); @@ -1116,4 +1116,10 @@ frappe.provide("frappe.views"); }); return flag; } + + function remove_img_tags(html) { + const $temp = $(`
        ${html}
        `) + $temp.find('img').remove(); + return $temp.html(); + } })(); diff --git a/frappe/public/js/frappe/views/reports/grid_report.js b/frappe/public/js/frappe/views/reports/grid_report.js index aa5d69d8c9..504708a12c 100644 --- a/frappe/public/js/frappe/views/reports/grid_report.js +++ b/frappe/public/js/frappe/views/reports/grid_report.js @@ -177,7 +177,7 @@ frappe.views.GridReport = Class.extend({ frappe.ui.get_print_settings(false, function(print_settings) { frappe.render_grid({grid: me.grid, title: me.page.title, print_settings: print_settings }); }); - + }, true); // range @@ -672,10 +672,11 @@ frappe.views.GridReportWithPlot = frappe.views.GridReport.extend({ } var chart_data = this.get_chart_data ? this.get_chart_data() : null; - this.chart = new frappe.ui.Chart({ - wrapper: this.chart_area, + this.chart = new frappe.chart.FrappeChart({ + parent: ".chart", + height: 200, data: chart_data, - x_type: 'timeseries' + type: 'line' }); }, @@ -701,29 +702,30 @@ frappe.views.GridReportWithPlot = frappe.views.GridReport.extend({ get_chart_data: function() { var me = this; - var plottable_cols = []; $.each(me.columns, function(idx, col) { if(col.formatter==me.currency_formatter && !col.hidden && col.plot!==false) { plottable_cols.push(col.field); } - }) + }); var data = { - x: 'x', - 'columns': [['x'].concat(plottable_cols)] + labels: plottable_cols, + datasets: [] }; $.each(this.data, function(i, item) { if (item.checked) { - var data_points = [item.name]; + let dataset = {}; + dataset.title = item.name; + dataset.values = []; $.each(plottable_cols, function(idx, col) { - data_points.push(item[col]); - }) - data["columns"].push(data_points); + dataset.values.push(item[col]); + }); + data["datasets"].push(dataset); } }); - return data + return data; } }); diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index dee3171d8c..54dda994f5 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -44,7 +44,7 @@ frappe.views.QueryReport = Class.extend({ this.wrapper = $("
        ").appendTo(this.page.main); $('\ \ -
        \ +
        \ ').appendTo(this.wrapper); this.wrapper.find(".expand-all").on("click", function() { me.toggle_all(false);}); this.wrapper.find(".collapse-all").on("click", function() { me.toggle_all(true);}); - this.chart_area = this.wrapper.find(".chart_area"); + this.chart_area = this.wrapper.find(".chart-area"); this.make_toolbar(); }, toggle_expand_collapse_buttons: function(show) { @@ -939,12 +939,13 @@ frappe.views.QueryReport = Class.extend({ } $.extend(opts, { - wrapper: this.chart_area, + parent: ".chart-area", + height: 200 }); - this.chart = new frappe.ui.Chart(opts); - if(this.chart && opts.data && opts.data.rows && opts.data.rows.length) { + if(opts.data && opts.data.labels && opts.data.labels.length) { this.chart_area.toggle(true); + this.chart = new frappe.chart.FrappeChart(opts); } }, diff --git a/frappe/public/js/frappe/views/treeview.js b/frappe/public/js/frappe/views/treeview.js index bbe176223d..5b9e17b512 100644 --- a/frappe/public/js/frappe/views/treeview.js +++ b/frappe/public/js/frappe/views/treeview.js @@ -43,6 +43,7 @@ frappe.views.TreeView = Class.extend({ this.get_root(); } + this.onload(); this.set_menu_item(); this.set_primary_action(); }, @@ -80,6 +81,10 @@ frappe.views.TreeView = Class.extend({ this.body = this.page.main; } }, + onload: function() { + var me = this; + this.opts.onload && this.opts.onload(me); + }, make_filters: function(){ var me = this; frappe.treeview_settings.filters = [] diff --git a/frappe/public/js/legacy/clientscriptAPI.js b/frappe/public/js/legacy/clientscriptAPI.js index c7590c5ae5..dc715defe8 100644 --- a/frappe/public/js/legacy/clientscriptAPI.js +++ b/frappe/public/js/legacy/clientscriptAPI.js @@ -492,7 +492,8 @@ _f.Frm.prototype.make_new = function(doctype) { } }); - frappe.set_route('Form', doctype, new_doc.name); + frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); + // frappe.set_route('Form', doctype, new_doc.name); }); } } diff --git a/frappe/public/js/legacy/form.js b/frappe/public/js/legacy/form.js index 413e1e594a..b526f9b6c8 100644 --- a/frappe/public/js/legacy/form.js +++ b/frappe/public/js/legacy/form.js @@ -225,7 +225,7 @@ _f.Frm.prototype.watch_model_updates = function() { }; _f.Frm.prototype.setup_std_layout = function() { - this.form_wrapper = $('
        ').appendTo(this.layout_main); + this.form_wrapper = $('
        ').appendTo(this.layout_main); this.body = $('
        ').appendTo(this.form_wrapper); // only tray diff --git a/frappe/public/js/lib/JsBarcode.all.min.js b/frappe/public/js/lib/JsBarcode.all.min.js new file mode 100644 index 0000000000..0d3e3c4a37 --- /dev/null +++ b/frappe/public/js/lib/JsBarcode.all.min.js @@ -0,0 +1,2 @@ +/*! JsBarcode v3.8.0 | (c) Johan Lindell | MIT license */ +!function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var n={};e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=17)}([function(t,e,n){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var o=function t(e,n){r(this,t),this.data=e,this.text=n.text||e,this.options=n};e.default=o},function(t,e,n){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var o=function(){function t(t,e){for(var n=0;n=200){i=t.shift()-105;var a=f.SWAP[i];void 0!==a?o=e.next(t,n+1,a):(r!==f.SET_A&&r!==f.SET_B||i!==f.SHIFT||(t[0]=r===f.SET_A?t[0]>95?t[0]-96:t[0]:t[0]<32?t[0]+96:t[0]),o=e.next(t,n+1,r))}else i=e.correctIndex(t,r),o=e.next(t,n+1,r);var u=e.getBar(i),s=i*n;return{result:u+o.result,checksum:s+o.checksum}}}]),e}(s.default);e.default=c},function(t,e,n){"use strict";function r(t){for(var e=0,n=0;n10*n.width?r.fontSize=10*n.width:r.fontSize=n.fontSize,r.guardHeight=n.height+r.fontSize/2+n.textMargin,r}return a(e,t),s(e,[{key:"valid",value:function(){return-1!==this.data.search(/^[0-9]{12}$/)&&this.data[11]==u(this.data)}},{key:"encode",value:function(){return this.options.flat?this.flatEncoding():this.guardedEncoding()}},{key:"flatEncoding",value:function(){var t=new c.default,e="";return e+="101",e+=t.encode(this.data.substr(0,6),"LLLLLL"),e+="01010",e+=t.encode(this.data.substr(6,6),"RRRRRR"),e+="101",{data:e,text:this.text}}},{key:"guardedEncoding",value:function(){var t=new c.default,e=[];return this.displayValue&&e.push({data:"00000000",text:this.text.substr(0,1),options:{textAlign:"left",fontSize:this.fontSize}}),e.push({data:"101"+t.encode(this.data[0],"L"),options:{height:this.guardHeight}}),e.push({data:t.encode(this.data.substr(1,5),"LLLLL"),text:this.text.substr(1,5),options:{fontSize:this.fontSize}}),e.push({data:"01010",options:{height:this.guardHeight}}),e.push({data:t.encode(this.data.substr(6,5),"RRRRR"),text:this.text.substr(6,5),options:{fontSize:this.fontSize}}),e.push({data:t.encode(this.data[11],"R")+"101",options:{height:this.guardHeight}}),this.displayValue&&e.push({data:"00000000",text:this.text.substr(11,1),options:{textAlign:"right",fontSize:this.fontSize}}),e}}]),e}(d.default);e.default=p},function(t,e,n){"use strict";function r(t,e){return e.height+(e.displayValue&&t.text.length>0?e.fontSize+e.textMargin:0)+e.marginTop+e.marginBottom}function o(t,e,n){if(n.displayValue&&ee&&(e=t[n].height);return e}function s(t,e,n){var r;if(n)r=n;else{if("undefined"==typeof document)return 0;r=document.createElement("canvas").getContext("2d")}return r.font=e.fontOptions+" "+e.fontSize+"px "+e.font,r.measureText(t).width}Object.defineProperty(e,"__esModule",{value:!0}),e.getTotalWidthOfEncodings=e.calculateEncodingAttributes=e.getBarcodePadding=e.getEncodingHeight=e.getMaximumHeightOfEncodings=void 0;var f=n(4),c=function(t){return t&&t.__esModule?t:{default:t}}(f);e.getMaximumHeightOfEncodings=u,e.getEncodingHeight=r,e.getBarcodePadding=o,e.calculateEncodingAttributes=i,e.getTotalWidthOfEncodings=a},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(24),o=n(23),i=n(30),a=n(33),u=n(32),s=n(38),f=n(40),c=n(39),l=n(31);e.default={CODE39:r.CODE39,CODE128:o.CODE128,CODE128A:o.CODE128A,CODE128B:o.CODE128B,CODE128C:o.CODE128C,EAN13:i.EAN13,EAN8:i.EAN8,EAN5:i.EAN5,EAN2:i.EAN2,UPC:i.UPC,UPCE:i.UPCE,ITF14:a.ITF14,ITF:u.ITF,MSI:s.MSI,MSI10:s.MSI10,MSI11:s.MSI11,MSI1010:s.MSI1010,MSI1110:s.MSI1110,pharmacode:f.pharmacode,codabar:c.codabar,GenericBarcode:l.GenericBarcode}},function(t,e,n){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var o=function(){function t(t,e){for(var n=0;n=u(t);return e+String.fromCharCode(o?206:205)+r(t,o)}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),a=function(t){return t.match(new RegExp("^"+i.A_CHARS+"*"))[0].length},u=function(t){return t.match(new RegExp("^"+i.B_CHARS+"*"))[0].length},s=function(t){return t.match(new RegExp("^"+i.C_CHARS+"*"))[0]};e.default=function(t){var e=void 0;if(s(t).length>=2)e=i.C_START_CHAR+o(t);else{var n=a(t)>u(t);e=(n?i.A_START_CHAR:i.B_START_CHAR)+r(t,n)}return e.replace(/[\xCD\xCE]([^])[\xCD\xCE]/,function(t,e){return String.fromCharCode(203)+e})}},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0}),e.CODE128C=e.CODE128B=e.CODE128A=e.CODE128=void 0;var o=n(21),i=r(o),a=n(18),u=r(a),s=n(19),f=r(s),c=n(20),l=r(c);e.CODE128=i.default,e.CODE128A=u.default,e.CODE128B=f.default,e.CODE128C=l.default},function(t,e,n){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function a(t){return u(f(t))}function u(t){return b[t].toString(2)}function s(t){return y[t]}function f(t){return y.indexOf(t)}function c(t){for(var e=0,n=0;n10*n.width?r.fontSize=10*n.width:r.fontSize=n.fontSize,r.guardHeight=n.height+r.fontSize/2+n.textMargin,r.lastChar=n.lastChar,r}return a(e,t),s(e,[{key:"valid",value:function(){return-1!==this.data.search(/^[0-9]{13}$/)&&this.data[12]==u(this.data)}},{key:"encode",value:function(){return this.options.flat?this.flatEncoding():this.guardedEncoding()}},{key:"getStructure",value:function(){return["LLLLLL","LLGLGG","LLGGLG","LLGGGL","LGLLGG","LGGLLG","LGGGLL","LGLGLG","LGLGGL","LGGLGL"]}},{key:"guardedEncoding",value:function(){var t=new c.default,e=[],n=this.getStructure()[this.data[0]],r=this.data.substr(1,6),o=this.data.substr(7,6);return this.options.displayValue&&e.push({data:"000000000000",text:this.text.substr(0,1),options:{textAlign:"left",fontSize:this.fontSize}}),e.push({data:"101",options:{height:this.guardHeight}}),e.push({data:t.encode(r,n),text:this.text.substr(1,6),options:{fontSize:this.fontSize}}),e.push({data:"01010",options:{height:this.guardHeight}}),e.push({data:t.encode(o,"RRRRRR"),text:this.text.substr(7,6),options:{fontSize:this.fontSize}}),e.push({data:"101",options:{height:this.guardHeight}}),this.options.lastChar&&this.options.displayValue&&(e.push({data:"00"}),e.push({data:"00000",text:this.options.lastChar,options:{fontSize:this.fontSize}})),e}},{key:"flatEncoding",value:function(){var t=new c.default,e="",n=this.getStructure()[this.data[0]];return e+="101",e+=t.encode(this.data.substr(1,6),n),e+="01010",e+=t.encode(this.data.substr(7,6),"RRRRRR"),e+="101",{data:e,text:this.text}}}]),e}(d.default);e.default=p},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function a(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(e,"__esModule",{value:!0});var u=function(){function t(t,e){for(var n=0;n10*n.width?r.fontSize=10*n.width:r.fontSize=n.fontSize,r.guardHeight=n.height+r.fontSize/2+n.textMargin,r}return a(e,t),s(e,[{key:"valid",value:function(){return this.isValid}},{key:"encode",value:function(){return this.options.flat?this.flatEncoding():this.guardedEncoding()}},{key:"flatEncoding",value:function(){var t=new c.default,e="";return e+="101",e+=this.encodeMiddleDigits(t),e+="010101",{data:e,text:this.text}}},{key:"guardedEncoding",value:function(){var t=new c.default,e=[];return this.displayValue&&e.push({data:"00000000",text:this.text[0],options:{textAlign:"left",fontSize:this.fontSize}}),e.push({data:"101",options:{height:this.guardHeight}}),e.push({data:this.encodeMiddleDigits(t),text:this.text.substring(1,7),options:{fontSize:this.fontSize}}),e.push({data:"010101",options:{height:this.guardHeight}}),this.displayValue&&e.push({data:"00000000",text:this.text[7],options:{textAlign:"right",fontSize:this.fontSize}}),e}},{key:"encodeMiddleDigits",value:function(t){var e=this.upcA[0],n=this.upcA[this.upcA.length-1],r=y[parseInt(n)][parseInt(e)];return t.encode(this.middleDigits,r)}}]),e}(d.default);e.default=b},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0}),e.UPCE=e.UPC=e.EAN2=e.EAN5=e.EAN8=e.EAN13=void 0;var o=n(25),i=r(o),a=n(28),u=r(a),s=n(27),f=r(s),c=n(26),l=r(c),d=n(10),p=r(d),h=n(29),y=r(h);e.EAN13=i.default,e.EAN8=u.default,e.EAN5=f.default,e.EAN2=l.default,e.UPC=p.default,e.UPCE=y.default},function(t,e,n){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(e,"__esModule",{value:!0}),e.GenericBarcode=void 0;var a=function(){function t(t,e){for(var n=0;n=3&&this.number<=131070}}]),e}(s.default);e.pharmacode=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}function o(t){var e={};for(var n in s.default)s.default.hasOwnProperty(n)&&(t.hasAttribute("jsbarcode-"+n.toLowerCase())&&(e[n]=t.getAttribute("jsbarcode-"+n.toLowerCase())),t.hasAttribute("data-"+n.toLowerCase())&&(e[n]=t.getAttribute("data-"+n.toLowerCase())));return e.value=t.getAttribute("jsbarcode-value")||t.getAttribute("data-value"),e=(0,a.default)(e)}Object.defineProperty(e,"__esModule",{value:!0});var i=n(8),a=r(i),u=n(9),s=r(u);e.default=o},function(t,e,n){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var o=function(){function t(t,e){for(var n=0;n0?(o=0,n.textAlign="left"):"right"==t.textAlign?(o=e.width-1,n.textAlign="right"):(o=e.width/2,n.textAlign="center"),n.fillText(e.text,o,i)}}},{key:"moveCanvasDrawing",value:function(t){this.canvas.getContext("2d").translate(t.width,0)}},{key:"restoreCanvas",value:function(){this.canvas.getContext("2d").restore()}}]),t}();e.default=s},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var o=n(42),i=r(o),a=n(45),u=r(a),s=n(44),f=r(s);e.default={CanvasRenderer:i.default,SVGRenderer:u.default,ObjectRenderer:f.default}},function(t,e,n){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var o=function(){function t(t,e){for(var n=0;n0&&(this.drawRect(a-e.width*i,r,e.width*i,e.height,t),i=0);i>0&&this.drawRect(a-e.width*(i-1),r,e.width*i,e.height,t)}},{key:"drawSVGText",value:function(t,e,n){var r=this.document.createElementNS(s,"text");if(e.displayValue){var o,i;r.setAttribute("style","font:"+e.fontOptions+" "+e.fontSize+"px "+e.font),i="top"==e.textPosition?e.fontSize-e.textMargin:e.height+e.textMargin+e.fontSize,"left"==e.textAlign||n.barcodePadding>0?(o=0,r.setAttribute("text-anchor","start")):"right"==e.textAlign?(o=n.width-1,r.setAttribute("text-anchor","end")):(o=n.width/2,r.setAttribute("text-anchor","middle")),r.setAttribute("x",o),r.setAttribute("y",i),r.appendChild(this.document.createTextNode(n.text)),t.appendChild(r)}}},{key:"setSvgAttributes",value:function(t,e){var n=this.svg;n.setAttribute("width",t+"px"),n.setAttribute("height",e+"px"),n.setAttribute("x","0px"),n.setAttribute("y","0px"),n.setAttribute("viewBox","0 0 "+t+" "+e),n.setAttribute("xmlns",s),n.setAttribute("version","1.1"),n.setAttribute("style","transform: translate(0,0)")}},{key:"createGroup",value:function(t,e,n){var r=this.document.createElementNS(s,"g");return r.setAttribute("transform","translate("+t+", "+e+")"),n.appendChild(r),r}},{key:"setGroupOptions",value:function(t,e){t.setAttribute("style","fill:"+e.lineColor+";")}},{key:"drawRect",value:function(t,e,n,r,o){var i=this.document.createElementNS(s,"rect");return i.setAttribute("x",t),i.setAttribute("y",e),i.setAttribute("width",n),i.setAttribute("height",r),o.appendChild(i),i}}]),t}();e.default=f}]); \ No newline at end of file diff --git a/frappe/public/js/lib/c3.min.js b/frappe/public/js/lib/c3.min.js deleted file mode 100644 index 94ae98446d..0000000000 --- a/frappe/public/js/lib/c3.min.js +++ /dev/null @@ -1,5 +0,0 @@ -!function(a){"use strict";function b(a){this.owner=a}function c(a,b){if(Object.create)b.prototype=Object.create(a.prototype);else{var c=function(){};c.prototype=a.prototype,b.prototype=new c}return b.prototype.constructor=b,b}function d(a){var b=this.internal=new e(this);b.loadConfig(a),b.beforeInit(a),b.init(),b.afterInit(a),function c(a,b,d){Object.keys(a).forEach(function(e){b[e]=a[e].bind(d),Object.keys(a[e]).length>0&&c(a[e],b[e],d)})}(h,this,this)}function e(b){var c=this;c.d3=a.d3?a.d3:"undefined"!=typeof require?require("d3"):void 0,c.api=b,c.config=c.getDefaultConfig(),c.data={},c.cache={},c.axes={}}function f(a){b.call(this,a)}function g(a,b){function c(a,b){a.attr("transform",function(a){return"translate("+Math.ceil(b(a)+u)+", 0)"})}function d(a,b){a.attr("transform",function(a){return"translate(0,"+Math.ceil(b(a))+")"})}function e(a){var b=a[0],c=a[a.length-1];return c>b?[b,c]:[c,b]}function f(a){var b,c,d=[];if(a.ticks)return a.ticks.apply(a,n);for(c=a.domain(),b=Math.ceil(c[0]);b0&&d[0]>0&&d.unshift(d[0]-(d[1]-d[0])),d}function g(){var a,c=p.copy();return b.isCategory&&(a=p.domain(),c.domain([a[0],a[1]-1])),c}function h(a){var b=m?m(a):a;return"undefined"!=typeof b?b:""}function i(a){if(z)return z;var b={h:11.5,w:5.5};return a.select("text").text(h).each(function(a){var c=this.getBoundingClientRect(),d=h(a),e=c.height,f=d?c.width/d.length:void 0;e&&f&&(b.h=e,b.w=f)}).text(""),z=b,b}function j(c){return b.withoutTransition?c:a.transition(c)}function k(m){m.each(function(){function m(a,c){function d(a,b){f=void 0;for(var h=1;hc)return d(a.concat(b.substr(0,f?f:h)),b.slice(f?f+1:h));return a.concat(b)}var e,f,g,i=h(a),j=[];return"[object Array]"===Object.prototype.toString.call(i)?i:((!c||0>=c)&&(c=X?95:b.isCategory?Math.ceil(F(G[1])-F(G[0]))-12:110),d(j,i+""))}function n(a,b){var c=U.h;return 0===b&&(c="left"===q||"right"===q?-((V[a.index]-1)*(U.h/2)-3):".71em"),c}function v(a){var b=p(a)+(o?0:u);return L[0]0?"start":"end":"middle"}function x(a){return a?"rotate("+a+")":""}function y(a){return a?8*Math.sin(Math.PI*(a/180)):0}function z(a){return a?11.5-2.5*(a/15)*(a>0?1:-1):W}var A,B,C,D=k.g=a.select(this),E=this.__chart__||p,F=this.__chart__=g(),G=t?t:f(F),H=D.selectAll(".tick").data(G,F),I=H.enter().insert("g",".domain").attr("class","tick").style("opacity",1e-6),J=H.exit().remove(),K=j(H).style("opacity",1),L=p.rangeExtent?p.rangeExtent():e(p.range()),M=D.selectAll(".domain").data([0]),N=(M.enter().append("path").attr("class","domain"),j(M));I.append("line"),I.append("text");var O=I.select("line"),P=K.select("line"),Q=I.select("text"),R=K.select("text");b.isCategory?(u=Math.ceil((F(1)-F(0))/2),B=o?0:u,C=o?u:0):u=B=0;var S,T,U=i(D.select(".tick")),V=[],W=Math.max(r,0)+s,X="left"===q||"right"===q;S=H.select("text"),T=S.selectAll("tspan").data(function(a,c){var d=b.tickMultiline?m(a,b.tickWidth):[].concat(h(a));return V[c]=d.length,d.map(function(a){return{index:c,splitted:a}})}),T.enter().append("tspan"),T.exit().remove(),T.text(function(a){return a.splitted});var Y=b.tickTextRotate;switch(q){case"bottom":A=c,O.attr("y2",r),Q.attr("y",W),P.attr("x1",B).attr("x2",B).attr("y2",v),R.attr("x",0).attr("y",z(Y)).style("text-anchor",w(Y)).attr("transform",x(Y)),T.attr("x",0).attr("dy",n).attr("dx",y(Y)),N.attr("d","M"+L[0]+","+l+"V0H"+L[1]+"V"+l);break;case"top":A=c,O.attr("y2",-r),Q.attr("y",-W),P.attr("x2",0).attr("y2",-r),R.attr("x",0).attr("y",-W),S.style("text-anchor","middle"),T.attr("x",0).attr("dy","0em"),N.attr("d","M"+L[0]+","+-l+"V0H"+L[1]+"V"+-l);break;case"left":A=d,O.attr("x2",-r),Q.attr("x",-W),P.attr("x2",-r).attr("y1",C).attr("y2",C),R.attr("x",-W).attr("y",u),S.style("text-anchor","end"),T.attr("x",-W).attr("dy",n),N.attr("d","M"+-l+","+L[0]+"H0V"+L[1]+"H"+-l);break;case"right":A=d,O.attr("x2",r),Q.attr("x",W),P.attr("x2",r).attr("y2",0),R.attr("x",W).attr("y",0),S.style("text-anchor","start"),T.attr("x",W).attr("dy",n),N.attr("d","M"+l+","+L[0]+"H0V"+L[1]+"H"+l)}if(F.rangeBand){var Z=F,$=Z.rangeBand()/2;E=F=function(a){return Z(a)+$}}else E.rangeBand?E=F:J.call(A,F);I.call(A,E),K.call(A,F)})}var l,m,n,o,p=a.scale.linear(),q="bottom",r=6,s=3,t=null,u=0,v=!0;return b=b||{},l=b.withOuterTick?6:0,k.scale=function(a){return arguments.length?(p=a,k):p},k.orient=function(a){return arguments.length?(q=a in{top:1,right:1,bottom:1,left:1}?a+"":"bottom",k):q},k.tickFormat=function(a){return arguments.length?(m=a,k):m},k.tickCentered=function(a){return arguments.length?(o=a,k):o},k.tickOffset=function(){return u},k.tickInterval=function(){var a,c;return b.isCategory?a=2*u:(c=k.g.select("path.domain").node().getTotalLength()-2*l,a=c/k.g.selectAll("line").size()),a===1/0?0:a},k.ticks=function(){return arguments.length?(n=arguments,k):n},k.tickCulling=function(a){return arguments.length?(v=a,k):v},k.tickValues=function(a){if("function"==typeof a)t=function(){return a(p.domain())};else{if(!arguments.length)return t;t=a}return k},k}var h,i,j,k={version:"0.4.11-rc4"};k.generate=function(a){return new d(a)},k.chart={fn:d.prototype,internal:{fn:e.prototype,axis:{fn:f.prototype}}},h=k.chart.fn,i=k.chart.internal.fn,j=k.chart.internal.axis.fn,i.beforeInit=function(){},i.afterInit=function(){},i.init=function(){var a=this,b=a.config;if(a.initParams(),b.data_url)a.convertUrlToData(b.data_url,b.data_mimeType,b.data_headers,b.data_keys,a.initWithData);else if(b.data_json)a.initWithData(a.convertJsonToData(b.data_json,b.data_keys));else if(b.data_rows)a.initWithData(a.convertRowsToData(b.data_rows));else{if(!b.data_columns)throw Error("url or json or rows or columns is required.");a.initWithData(a.convertColumnsToData(b.data_columns))}},i.initParams=function(){var a=this,b=a.d3,c=a.config;a.clipId="c3-"+ +new Date+"-clip",a.clipIdForXAxis=a.clipId+"-xaxis",a.clipIdForYAxis=a.clipId+"-yaxis",a.clipIdForGrid=a.clipId+"-grid",a.clipIdForSubchart=a.clipId+"-subchart",a.clipPath=a.getClipPath(a.clipId),a.clipPathForXAxis=a.getClipPath(a.clipIdForXAxis),a.clipPathForYAxis=a.getClipPath(a.clipIdForYAxis),a.clipPathForGrid=a.getClipPath(a.clipIdForGrid),a.clipPathForSubchart=a.getClipPath(a.clipIdForSubchart),a.dragStart=null,a.dragging=!1,a.flowing=!1,a.cancelClick=!1,a.mouseover=!1,a.transiting=!1,a.color=a.generateColor(),a.levelColor=a.generateLevelColor(),a.dataTimeFormat=c.data_xLocaltime?b.time.format:b.time.format.utc,a.axisTimeFormat=c.axis_x_localtime?b.time.format:b.time.format.utc,a.defaultAxisTimeFormat=a.axisTimeFormat.multi([[".%L",function(a){return a.getMilliseconds()}],[":%S",function(a){return a.getSeconds()}],["%I:%M",function(a){return a.getMinutes()}],["%I %p",function(a){return a.getHours()}],["%-m/%-d",function(a){return a.getDay()&&1!==a.getDate()}],["%-m/%-d",function(a){return 1!==a.getDate()}],["%-m/%-d",function(a){return a.getMonth()}],["%Y/%-m/%-d",function(){return!0}]]),a.hiddenTargetIds=[],a.hiddenLegendIds=[],a.focusedTargetIds=[],a.defocusedTargetIds=[],a.xOrient=c.axis_rotated?"left":"bottom",a.yOrient=c.axis_rotated?c.axis_y_inner?"top":"bottom":c.axis_y_inner?"right":"left",a.y2Orient=c.axis_rotated?c.axis_y2_inner?"bottom":"top":c.axis_y2_inner?"left":"right",a.subXOrient=c.axis_rotated?"left":"bottom",a.isLegendRight="right"===c.legend_position,a.isLegendInset="inset"===c.legend_position,a.isLegendTop="top-left"===c.legend_inset_anchor||"top-right"===c.legend_inset_anchor,a.isLegendLeft="top-left"===c.legend_inset_anchor||"bottom-left"===c.legend_inset_anchor,a.legendStep=0,a.legendItemWidth=0,a.legendItemHeight=0,a.currentMaxTickWidths={x:0,y:0,y2:0},a.rotated_padding_left=30,a.rotated_padding_right=c.axis_rotated&&!c.axis_x_show?0:30,a.rotated_padding_top=5,a.withoutFadeIn={},a.intervalForObserveInserted=void 0,a.axes.subx=b.selectAll([])},i.initChartElements=function(){this.initBar&&this.initBar(),this.initLine&&this.initLine(),this.initArc&&this.initArc(),this.initGauge&&this.initGauge(),this.initText&&this.initText()},i.initWithData=function(a){var b,c,d=this,e=d.d3,g=d.config,h=!0;d.axis=new f(d),d.initPie&&d.initPie(),d.initBrush&&d.initBrush(),d.initZoom&&d.initZoom(),g.bindto?"function"==typeof g.bindto.node?d.selectChart=g.bindto:d.selectChart=e.select(g.bindto):d.selectChart=e.selectAll([]),d.selectChart.empty()&&(d.selectChart=e.select(document.createElement("div")).style("opacity",0),d.observeInserted(d.selectChart),h=!1),d.selectChart.html("").classed("c3",!0),d.data.xs={},d.data.targets=d.convertDataToTargets(a),g.data_filter&&(d.data.targets=d.data.targets.filter(g.data_filter)),g.data_hide&&d.addHiddenTargetIds(g.data_hide===!0?d.mapToIds(d.data.targets):g.data_hide),g.legend_hide&&d.addHiddenLegendIds(g.legend_hide===!0?d.mapToIds(d.data.targets):g.legend_hide),d.hasType("gauge")&&(g.legend_show=!1),d.updateSizes(),d.updateScales(),d.x.domain(e.extent(d.getXDomain(d.data.targets))),d.y.domain(d.getYDomain(d.data.targets,"y")),d.y2.domain(d.getYDomain(d.data.targets,"y2")),d.subX.domain(d.x.domain()),d.subY.domain(d.y.domain()),d.subY2.domain(d.y2.domain()),d.orgXDomain=d.x.domain(),d.brush&&d.brush.scale(d.subX),g.zoom_enabled&&d.zoom.scale(d.x),d.svg=d.selectChart.append("svg").style("overflow","hidden").on("mouseenter",function(){return g.onmouseover.call(d)}).on("mouseleave",function(){return g.onmouseout.call(d)}),d.config.svg_classname&&d.svg.attr("class",d.config.svg_classname),b=d.svg.append("defs"),d.clipChart=d.appendClip(b,d.clipId),d.clipXAxis=d.appendClip(b,d.clipIdForXAxis),d.clipYAxis=d.appendClip(b,d.clipIdForYAxis),d.clipGrid=d.appendClip(b,d.clipIdForGrid),d.clipSubchart=d.appendClip(b,d.clipIdForSubchart),d.updateSvgSize(),c=d.main=d.svg.append("g").attr("transform",d.getTranslate("main")),d.initSubchart&&d.initSubchart(),d.initTooltip&&d.initTooltip(),d.initLegend&&d.initLegend(),d.initTitle&&d.initTitle(),c.append("text").attr("class",l.text+" "+l.empty).attr("text-anchor","middle").attr("dominant-baseline","middle"),d.initRegion(),d.initGrid(),c.append("g").attr("clip-path",d.clipPath).attr("class",l.chart),g.grid_lines_front&&d.initGridLines(),d.initEventRect(),d.initChartElements(),c.insert("rect",g.zoom_privileged?null:"g."+l.regions).attr("class",l.zoomRect).attr("width",d.width).attr("height",d.height).style("opacity",0).on("dblclick.zoom",null),g.axis_x_extent&&d.brush.extent(d.getDefaultExtent()),d.axis.init(),d.updateTargets(d.data.targets),h&&(d.updateDimension(),d.config.oninit.call(d),d.redraw({withTransition:!1,withTransform:!0,withUpdateXDomain:!0,withUpdateOrgXDomain:!0,withTransitionForAxis:!1})),d.bindResize(),d.api.element=d.selectChart.node()},i.smoothLines=function(a,b){var c=this;"grid"===b&&a.each(function(){var a=c.d3.select(this),b=a.attr("x1"),d=a.attr("x2"),e=a.attr("y1"),f=a.attr("y2");a.attr({x1:Math.ceil(b),x2:Math.ceil(d),y1:Math.ceil(e),y2:Math.ceil(f)})})},i.updateSizes=function(){var a=this,b=a.config,c=a.legend?a.getLegendHeight():0,d=a.legend?a.getLegendWidth():0,e=a.isLegendRight||a.isLegendInset?0:c,f=a.hasArcType(),g=b.axis_rotated||f?0:a.getHorizontalAxisHeight("x"),h=b.subchart_show&&!f?b.subchart_size_height+g:0;a.currentWidth=a.getCurrentWidth(),a.currentHeight=a.getCurrentHeight(),a.margin=b.axis_rotated?{top:a.getHorizontalAxisHeight("y2")+a.getCurrentPaddingTop(),right:f?0:a.getCurrentPaddingRight(),bottom:a.getHorizontalAxisHeight("y")+e+a.getCurrentPaddingBottom(),left:h+(f?0:a.getCurrentPaddingLeft())}:{top:4+a.getCurrentPaddingTop(),right:f?0:a.getCurrentPaddingRight(),bottom:g+h+e+a.getCurrentPaddingBottom(),left:f?0:a.getCurrentPaddingLeft()},a.margin2=b.axis_rotated?{top:a.margin.top,right:NaN,bottom:20+e,left:a.rotated_padding_left}:{top:a.currentHeight-h-e,right:NaN,bottom:g+e,left:a.margin.left},a.margin3={top:0,right:NaN,bottom:0,left:0},a.updateSizeForLegend&&a.updateSizeForLegend(c,d),a.width=a.currentWidth-a.margin.left-a.margin.right,a.height=a.currentHeight-a.margin.top-a.margin.bottom,a.width<0&&(a.width=0),a.height<0&&(a.height=0),a.width2=b.axis_rotated?a.margin.left-a.rotated_padding_left-a.rotated_padding_right:a.width,a.height2=b.axis_rotated?a.height:a.currentHeight-a.margin2.top-a.margin2.bottom,a.width2<0&&(a.width2=0),a.height2<0&&(a.height2=0),a.arcWidth=a.width-(a.isLegendRight?d+10:0),a.arcHeight=a.height-(a.isLegendRight?0:10),a.hasType("gauge")&&(a.arcHeight+=a.height-a.getGaugeLabelHeight()),a.updateRadius&&a.updateRadius(),a.isLegendRight&&f&&(a.margin3.left=a.arcWidth/2+1.1*a.radiusExpanded)},i.updateTargets=function(a){var b=this;b.updateTargetsForText(a),b.updateTargetsForBar(a),b.updateTargetsForLine(a),b.hasArcType()&&b.updateTargetsForArc&&b.updateTargetsForArc(a),b.updateTargetsForSubchart&&b.updateTargetsForSubchart(a),b.showTargets()},i.showTargets=function(){var a=this;a.svg.selectAll("."+l.target).filter(function(b){return a.isTargetToShow(b.id)}).transition().duration(a.config.transition_duration).style("opacity",1)},i.redraw=function(a,b){var c,d,e,f,g,h,i,j,k,m,n,o,p,q,r,s,t,u,v,x,y,z,A,B,C,D,E,F,G,H=this,I=H.main,J=H.d3,K=H.config,L=H.getShapeIndices(H.isAreaType),M=H.getShapeIndices(H.isBarType),N=H.getShapeIndices(H.isLineType),O=H.hasArcType(),P=H.filterTargetsToShow(H.data.targets),Q=H.xv.bind(H);if(a=a||{},c=w(a,"withY",!0),d=w(a,"withSubchart",!0),e=w(a,"withTransition",!0),h=w(a,"withTransform",!1),i=w(a,"withUpdateXDomain",!1),j=w(a,"withUpdateOrgXDomain",!1),k=w(a,"withTrimXDomain",!0),p=w(a,"withUpdateXAxis",i),m=w(a,"withLegend",!1),n=w(a,"withEventRect",!0),o=w(a,"withDimension",!0),f=w(a,"withTransitionForExit",e),g=w(a,"withTransitionForAxis",e),v=e?K.transition_duration:0,x=f?v:0,y=g?v:0,b=b||H.axis.generateTransitions(y),m&&K.legend_show?H.updateLegend(H.mapToIds(H.data.targets),a,b):o&&H.updateDimension(!0),H.isCategorized()&&0===P.length&&H.x.domain([0,H.axes.x.selectAll(".tick").size()]),P.length?(H.updateXDomain(P,i,j,k),K.axis_x_tick_values||(B=H.axis.updateXAxisTickValues(P))):(H.xAxis.tickValues([]),H.subXAxis.tickValues([])),K.zoom_rescale&&!a.flow&&(E=H.x.orgDomain()),H.y.domain(H.getYDomain(P,"y",E)),H.y2.domain(H.getYDomain(P,"y2",E)),!K.axis_y_tick_values&&K.axis_y_tick_count&&H.yAxis.tickValues(H.axis.generateTickValues(H.y.domain(),K.axis_y_tick_count)),!K.axis_y2_tick_values&&K.axis_y2_tick_count&&H.y2Axis.tickValues(H.axis.generateTickValues(H.y2.domain(),K.axis_y2_tick_count)),H.axis.redraw(b,O),H.axis.updateLabels(e),(i||p)&&P.length)if(K.axis_x_tick_culling&&B){for(C=1;C=0&&J.select(this).style("display",b%D?"none":"block")})}else H.svg.selectAll("."+l.axisX+" .tick text").style("display","block");q=H.generateDrawArea?H.generateDrawArea(L,!1):void 0,r=H.generateDrawBar?H.generateDrawBar(M):void 0,s=H.generateDrawLine?H.generateDrawLine(N,!1):void 0,t=H.generateXYForText(L,M,N,!0),u=H.generateXYForText(L,M,N,!1),c&&(H.subY.domain(H.getYDomain(P,"y")),H.subY2.domain(H.getYDomain(P,"y2"))),H.updateXgridFocus(),I.select("text."+l.text+"."+l.empty).attr("x",H.width/2).attr("y",H.height/2).text(K.data_empty_label_text).transition().style("opacity",P.length?0:1),H.updateGrid(v),H.updateRegion(v),H.updateBar(x),H.updateLine(x),H.updateArea(x),H.updateCircle(),H.hasDataLabel()&&H.updateText(x),H.redrawTitle&&H.redrawTitle(),H.redrawArc&&H.redrawArc(v,x,h),H.redrawSubchart&&H.redrawSubchart(d,b,v,x,L,M,N),I.selectAll("."+l.selectedCircles).filter(H.isBarType.bind(H)).selectAll("circle").remove(),K.interaction_enabled&&!a.flow&&n&&(H.redrawEventRect(),H.updateZoom&&H.updateZoom()),H.updateCircleY(),F=(H.config.axis_rotated?H.circleY:H.circleX).bind(H),G=(H.config.axis_rotated?H.circleX:H.circleY).bind(H),a.flow&&(A=H.generateFlow({targets:P,flow:a.flow,duration:a.flow.duration,drawBar:r,drawLine:s,drawArea:q,cx:F,cy:G,xv:Q,xForText:t,yForText:u})),(v||A)&&H.isTabVisible()?J.transition().duration(v).each(function(){var b=[];[H.redrawBar(r,!0),H.redrawLine(s,!0),H.redrawArea(q,!0),H.redrawCircle(F,G,!0),H.redrawText(t,u,a.flow,!0),H.redrawRegion(!0),H.redrawGrid(!0)].forEach(function(a){a.forEach(function(a){b.push(a)})}),z=H.generateWait(),b.forEach(function(a){z.add(a)})}).call(z,function(){A&&A(),K.onrendered&&K.onrendered.call(H)}):(H.redrawBar(r),H.redrawLine(s),H.redrawArea(q),H.redrawCircle(F,G),H.redrawText(t,u,a.flow),H.redrawRegion(),H.redrawGrid(),K.onrendered&&K.onrendered.call(H)),H.mapToIds(H.data.targets).forEach(function(a){H.withoutFadeIn[a]=!0})},i.updateAndRedraw=function(a){var b,c=this,d=c.config;a=a||{},a.withTransition=w(a,"withTransition",!0),a.withTransform=w(a,"withTransform",!1),a.withLegend=w(a,"withLegend",!1),a.withUpdateXDomain=!0,a.withUpdateOrgXDomain=!0,a.withTransitionForExit=!1,a.withTransitionForTransform=w(a,"withTransitionForTransform",a.withTransition),c.updateSizes(),a.withLegend&&d.legend_show||(b=c.axis.generateTransitions(a.withTransitionForAxis?d.transition_duration:0),c.updateScales(),c.updateSvgSize(),c.transformAll(a.withTransitionForTransform,b)),c.redraw(a,b)},i.redrawWithoutRescale=function(){this.redraw({withY:!1,withSubchart:!1,withEventRect:!1,withTransitionForAxis:!1})},i.isTimeSeries=function(){return"timeseries"===this.config.axis_x_type},i.isCategorized=function(){return this.config.axis_x_type.indexOf("categor")>=0},i.isCustomX=function(){var a=this,b=a.config;return!a.isTimeSeries()&&(b.data_x||v(b.data_xs))},i.isTimeSeriesY=function(){return"timeseries"===this.config.axis_y_type},i.getTranslate=function(a){var b,c,d=this,e=d.config;return"main"===a?(b=s(d.margin.left),c=s(d.margin.top)):"context"===a?(b=s(d.margin2.left),c=s(d.margin2.top)):"legend"===a?(b=d.margin3.left,c=d.margin3.top):"x"===a?(b=0,c=e.axis_rotated?0:d.height):"y"===a?(b=0,c=e.axis_rotated?d.height:0):"y2"===a?(b=e.axis_rotated?0:d.width,c=e.axis_rotated?1:0):"subx"===a?(b=0,c=e.axis_rotated?0:d.height2):"arc"===a&&(b=d.arcWidth/2,c=d.arcHeight/2),"translate("+b+","+c+")"},i.initialOpacity=function(a){return null!==a.value&&this.withoutFadeIn[a.id]?1:0},i.initialOpacityForCircle=function(a){return null!==a.value&&this.withoutFadeIn[a.id]?this.opacityForCircle(a):0},i.opacityForCircle=function(a){var b=this.config.point_show?1:0;return m(a.value)?this.isScatterType(a)?.5:b:0},i.opacityForText=function(){return this.hasDataLabel()?1:0},i.xx=function(a){return a?this.x(a.x):null},i.xv=function(a){var b=this,c=a.value;return b.isTimeSeries()?c=b.parseDate(a.value):b.isCategorized()&&"string"==typeof a.value&&(c=b.config.axis_x_categories.indexOf(a.value)),Math.ceil(b.x(c))},i.yv=function(a){var b=this,c=a.axis&&"y2"===a.axis?b.y2:b.y;return Math.ceil(c(a.value))},i.subxx=function(a){return a?this.subX(a.x):null},i.transformMain=function(a,b){var c,d,e,f=this;b&&b.axisX?c=b.axisX:(c=f.main.select("."+l.axisX),a&&(c=c.transition())),b&&b.axisY?d=b.axisY:(d=f.main.select("."+l.axisY),a&&(d=d.transition())),b&&b.axisY2?e=b.axisY2:(e=f.main.select("."+l.axisY2),a&&(e=e.transition())),(a?f.main.transition():f.main).attr("transform",f.getTranslate("main")),c.attr("transform",f.getTranslate("x")),d.attr("transform",f.getTranslate("y")),e.attr("transform",f.getTranslate("y2")),f.main.select("."+l.chartArcs).attr("transform",f.getTranslate("arc"))},i.transformAll=function(a,b){var c=this;c.transformMain(a,b),c.config.subchart_show&&c.transformContext(a,b),c.legend&&c.transformLegend(a)},i.updateSvgSize=function(){var a=this,b=a.svg.select(".c3-brush .background");a.svg.attr("width",a.currentWidth).attr("height",a.currentHeight),a.svg.selectAll(["#"+a.clipId,"#"+a.clipIdForGrid]).select("rect").attr("width",a.width).attr("height",a.height),a.svg.select("#"+a.clipIdForXAxis).select("rect").attr("x",a.getXAxisClipX.bind(a)).attr("y",a.getXAxisClipY.bind(a)).attr("width",a.getXAxisClipWidth.bind(a)).attr("height",a.getXAxisClipHeight.bind(a)),a.svg.select("#"+a.clipIdForYAxis).select("rect").attr("x",a.getYAxisClipX.bind(a)).attr("y",a.getYAxisClipY.bind(a)).attr("width",a.getYAxisClipWidth.bind(a)).attr("height",a.getYAxisClipHeight.bind(a)),a.svg.select("#"+a.clipIdForSubchart).select("rect").attr("width",a.width).attr("height",b.size()?b.attr("height"):0),a.svg.select("."+l.zoomRect).attr("width",a.width).attr("height",a.height),a.selectChart.style("max-height",a.currentHeight+"px")},i.updateDimension=function(a){var b=this;a||(b.config.axis_rotated?(b.axes.x.call(b.xAxis),b.axes.subx.call(b.subXAxis)):(b.axes.y.call(b.yAxis),b.axes.y2.call(b.y2Axis))),b.updateSizes(),b.updateScales(),b.updateSvgSize(),b.transformAll(!1)},i.observeInserted=function(b){var c,d=this;return"undefined"==typeof MutationObserver?void a.console.error("MutationObserver not defined."):(c=new MutationObserver(function(e){e.forEach(function(e){"childList"===e.type&&e.previousSibling&&(c.disconnect(),d.intervalForObserveInserted=a.setInterval(function(){b.node().parentNode&&(a.clearInterval(d.intervalForObserveInserted),d.updateDimension(),d.brush&&d.brush.update(),d.config.oninit.call(d),d.redraw({withTransform:!0,withUpdateXDomain:!0,withUpdateOrgXDomain:!0,withTransition:!1,withTransitionForTransform:!1,withLegend:!0}),b.transition().style("opacity",1))},10))})}),void c.observe(b.node(),{attributes:!0,childList:!0,characterData:!0}))},i.bindResize=function(){var b=this,c=b.config;if(b.resizeFunction=b.generateResize(),b.resizeFunction.add(function(){c.onresize.call(b)}),c.resize_auto&&b.resizeFunction.add(function(){void 0!==b.resizeTimeout&&a.clearTimeout(b.resizeTimeout),b.resizeTimeout=a.setTimeout(function(){delete b.resizeTimeout,b.api.flush()},100)}),b.resizeFunction.add(function(){c.onresized.call(b)}),a.attachEvent)a.attachEvent("onresize",b.resizeFunction);else if(a.addEventListener)a.addEventListener("resize",b.resizeFunction,!1);else{var d=a.onresize;d?d.add&&d.remove||(d=b.generateResize(),d.add(a.onresize)):d=b.generateResize(),d.add(b.resizeFunction),a.onresize=d}},i.generateResize=function(){function a(){b.forEach(function(a){a()})}var b=[];return a.add=function(a){b.push(a)},a.remove=function(a){for(var c=0;c0)for(g=h.hasNegativeValueInTargets(a),b=0;b=0}),0!==e.length)for(d=e[0],g&&k[d]&&k[d].forEach(function(a,b){k[d][b]=0>a?a:0}),c=1;c0||(k[d][b]+=+a)});return h.d3.min(Object.keys(k).map(function(a){return h.d3.min(k[a])}))},i.getYDomainMax=function(a){var b,c,d,e,f,g,h=this,i=h.config,j=h.mapToIds(a),k=h.getValuesAsIdKeyed(a);if(i.data_groups.length>0)for(g=h.hasPositiveValueInTargets(a),b=0;b=0}),0!==e.length)for(d=e[0],g&&k[d]&&k[d].forEach(function(a,b){k[d][b]=a>0?a:0}),c=1;c+a||(k[d][b]+=+a)});return h.d3.max(Object.keys(k).map(function(a){return h.d3.max(k[a])}))},i.getYDomain=function(a,b,c){var d,e,f,g,h,i,j,k,l,n,o,p=this,q=p.config,r=a.filter(function(a){return p.axis.getId(a.id)===b}),s=c?p.filterByXDomain(r,c):r,u="y2"===b?q.axis_y2_min:q.axis_y_min,w="y2"===b?q.axis_y2_max:q.axis_y_max,x=p.getYDomainMin(s),y=p.getYDomainMax(s),z="y2"===b?q.axis_y2_center:q.axis_y_center,A=p.hasType("bar",s)&&q.bar_zerobased||p.hasType("area",s)&&q.area_zerobased,B="y2"===b?q.axis_y2_inverted:q.axis_y_inverted,C=p.hasDataLabel()&&q.axis_rotated,D=p.hasDataLabel()&&!q.axis_rotated;return x=m(u)?u:m(w)?w>x?x:w-10:x,y=m(w)?w:m(u)?y>u?y:u+10:y,0===s.length?"y2"===b?p.y2.domain():p.y.domain():(isNaN(x)&&(x=0),isNaN(y)&&(y=x),x===y&&(0>x?y=0:x=0),n=x>=0&&y>=0,o=0>=x&&0>=y,(m(u)&&n||m(w)&&o)&&(A=!1),A&&(n&&(x=0),o&&(y=0)),e=Math.abs(y-x),f=g=h=.1*e,"undefined"!=typeof z&&(i=Math.max(Math.abs(x),Math.abs(y)),y=z+i,x=z-i),C?(j=p.getDataLabelLength(x,y,"width"),k=t(p.y.range()),l=[j[0]/k,j[1]/k],g+=e*(l[1]/(1-l[0]-l[1])),h+=e*(l[0]/(1-l[0]-l[1]))):D&&(j=p.getDataLabelLength(x,y,"height"),g+=p.axis.convertPixelsToAxisPadding(j[1],e), -h+=p.axis.convertPixelsToAxisPadding(j[0],e)),"y"===b&&v(q.axis_y_padding)&&(g=p.axis.getPadding(q.axis_y_padding,"top",g,e),h=p.axis.getPadding(q.axis_y_padding,"bottom",h,e)),"y2"===b&&v(q.axis_y2_padding)&&(g=p.axis.getPadding(q.axis_y2_padding,"top",g,e),h=p.axis.getPadding(q.axis_y2_padding,"bottom",h,e)),A&&(n&&(h=x),o&&(g=-y)),d=[x-h,y+g],B?d.reverse():d)},i.getXDomainMin=function(a){var b=this,c=b.config;return q(c.axis_x_min)?b.isTimeSeries()?this.parseDate(c.axis_x_min):c.axis_x_min:b.d3.min(a,function(a){return b.d3.min(a.values,function(a){return a.x})})},i.getXDomainMax=function(a){var b=this,c=b.config;return q(c.axis_x_max)?b.isTimeSeries()?this.parseDate(c.axis_x_max):c.axis_x_max:b.d3.max(a,function(a){return b.d3.max(a.values,function(a){return a.x})})},i.getXDomainPadding=function(a){var b,c,d,e,f=this,g=f.config,h=a[1]-a[0];return f.isCategorized()?c=0:f.hasType("bar")?(b=f.getMaxDataCount(),c=b>1?h/(b-1)/2:.5):c=.01*h,"object"==typeof g.axis_x_padding&&v(g.axis_x_padding)?(d=m(g.axis_x_padding.left)?g.axis_x_padding.left:c,e=m(g.axis_x_padding.right)?g.axis_x_padding.right:c):d=e="number"==typeof g.axis_x_padding?g.axis_x_padding:c,{left:d,right:e}},i.getXDomain=function(a){var b=this,c=[b.getXDomainMin(a),b.getXDomainMax(a)],d=c[0],e=c[1],f=b.getXDomainPadding(c),g=0,h=0;return d-e!==0||b.isCategorized()||(b.isTimeSeries()?(d=new Date(.5*d.getTime()),e=new Date(1.5*e.getTime())):(d=0===d?1:.5*d,e=0===e?-1:1.5*e)),(d||0===d)&&(g=b.isTimeSeries()?new Date(d.getTime()-f.left):d-f.left),(e||0===e)&&(h=b.isTimeSeries()?new Date(e.getTime()+f.right):e+f.right),[g,h]},i.updateXDomain=function(a,b,c,d,e){var f=this,g=f.config;return c&&(f.x.domain(e?e:f.d3.extent(f.getXDomain(a))),f.orgXDomain=f.x.domain(),g.zoom_enabled&&f.zoom.scale(f.x).updateScaleExtent(),f.subX.domain(f.x.domain()),f.brush&&f.brush.scale(f.subX)),b&&(f.x.domain(e?e:!f.brush||f.brush.empty()?f.orgXDomain:f.brush.extent()),g.zoom_enabled&&f.zoom.scale(f.x).updateScaleExtent()),d&&f.x.domain(f.trimXDomain(f.x.orgDomain())),f.x.domain()},i.trimXDomain=function(a){var b=this.getZoomDomain(),c=b[0],d=b[1];return a[0]<=c&&(a[1]=+a[1]+(c-a[0]),a[0]=c),d<=a[1]&&(a[0]=+a[0]-(a[1]-d),a[1]=d),a},i.isX=function(a){var b=this,c=b.config;return c.data_x&&a===c.data_x||v(c.data_xs)&&x(c.data_xs,a)},i.isNotX=function(a){return!this.isX(a)},i.getXKey=function(a){var b=this,c=b.config;return c.data_x?c.data_x:v(c.data_xs)?c.data_xs[a]:null},i.getXValuesOfXKey=function(a,b){var c,d=this,e=b&&v(b)?d.mapToIds(b):[];return e.forEach(function(b){d.getXKey(b)===a&&(c=d.data.xs[b])}),c},i.getIndexByX=function(a){var b=this,c=b.filterByX(b.data.targets,a);return c.length?c[0].index:null},i.getXValue=function(a,b){var c=this;return a in c.data.xs&&c.data.xs[a]&&m(c.data.xs[a][b])?c.data.xs[a][b]:b},i.getOtherTargetXs=function(){var a=this,b=Object.keys(a.data.xs);return b.length?a.data.xs[b[0]]:null},i.getOtherTargetX=function(a){var b=this.getOtherTargetXs();return b&&a1},i.isMultipleX=function(){return v(this.config.data_xs)||!this.config.data_xSort||this.hasType("scatter")},i.addName=function(a){var b,c=this;return a&&(b=c.config.data_names[a.id],a.name=void 0!==b?b:a.id),a},i.getValueOnIndex=function(a,b){var c=a.filter(function(a){return a.index===b});return c.length?c[0]:null},i.updateTargetX=function(a,b){var c=this;a.forEach(function(a){a.values.forEach(function(d,e){d.x=c.generateTargetX(b[e],a.id,e)}),c.data.xs[a.id]=b})},i.updateTargetXs=function(a,b){var c=this;a.forEach(function(a){b[a.id]&&c.updateTargetX([a],b[a.id])})},i.generateTargetX=function(a,b,c){var d,e=this;return d=e.isTimeSeries()?a?e.parseDate(a):e.parseDate(e.getXValue(b,c)):e.isCustomX()&&!e.isCategorized()?m(a)?+a:e.getXValue(b,c):c},i.cloneTarget=function(a){return{id:a.id,id_org:a.id_org,values:a.values.map(function(a){return{x:a.x,value:a.value,id:a.id}})}},i.updateXs=function(){var a=this;a.data.targets.length&&(a.xs=[],a.data.targets[0].values.forEach(function(b){a.xs[b.index]=b.x}))},i.getPrevX=function(a){var b=this.xs[a-1];return"undefined"!=typeof b?b:null},i.getNextX=function(a){var b=this.xs[a+1];return"undefined"!=typeof b?b:null},i.getMaxDataCount=function(){var a=this;return a.d3.max(a.data.targets,function(a){return a.values.length})},i.getMaxDataCountTarget=function(a){var b,c=a.length,d=0;return c>1?a.forEach(function(a){a.values.length>d&&(b=a,d=a.values.length)}):b=c?a[0]:null,b},i.getEdgeX=function(a){var b=this;return a.length?[b.d3.min(a,function(a){return a.values[0].x}),b.d3.max(a,function(a){return a.values[a.values.length-1].x})]:[0,0]},i.mapToIds=function(a){return a.map(function(a){return a.id})},i.mapToTargetIds=function(a){var b=this;return a?[].concat(a):b.mapToIds(b.data.targets)},i.hasTarget=function(a,b){var c,d=this.mapToIds(a);for(c=0;ca?-1:a>b?1:a>=b?0:NaN})},i.addHiddenTargetIds=function(a){this.hiddenTargetIds=this.hiddenTargetIds.concat(a)},i.removeHiddenTargetIds=function(a){this.hiddenTargetIds=this.hiddenTargetIds.filter(function(b){return a.indexOf(b)<0})},i.addHiddenLegendIds=function(a){this.hiddenLegendIds=this.hiddenLegendIds.concat(a)},i.removeHiddenLegendIds=function(a){this.hiddenLegendIds=this.hiddenLegendIds.filter(function(b){return a.indexOf(b)<0})},i.getValuesAsIdKeyed=function(a){var b={};return a.forEach(function(a){b[a.id]=[],a.values.forEach(function(c){b[a.id].push(c.value)})}),b},i.checkValueInTargets=function(a,b){var c,d,e,f=Object.keys(a);for(c=0;ca})},i.hasPositiveValueInTargets=function(a){return this.checkValueInTargets(a,function(a){return a>0})},i.isOrderDesc=function(){var a=this.config;return"string"==typeof a.data_order&&"desc"===a.data_order.toLowerCase()},i.isOrderAsc=function(){var a=this.config;return"string"==typeof a.data_order&&"asc"===a.data_order.toLowerCase()},i.orderTargets=function(a){var b=this,c=b.config,d=b.isOrderAsc(),e=b.isOrderDesc();return d||e?a.sort(function(a,b){var c=function(a,b){return a+Math.abs(b.value)},e=a.values.reduce(c,0),f=b.values.reduce(c,0);return d?f-e:e-f}):n(c.data_order)&&a.sort(c.data_order),a},i.filterByX=function(a,b){return this.d3.merge(a.map(function(a){return a.values})).filter(function(a){return a.x-b===0})},i.filterRemoveNull=function(a){return a.filter(function(a){return m(a.value)})},i.filterByXDomain=function(a,b){return a.map(function(a){return{id:a.id,id_org:a.id_org,values:a.values.filter(function(a){return b[0]<=a.x&&a.x<=b[1]})}})},i.hasDataLabel=function(){var a=this.config;return"boolean"==typeof a.data_labels&&a.data_labels?!0:"object"==typeof a.data_labels&&v(a.data_labels)?!0:!1},i.getDataLabelLength=function(a,b,c){var d=this,e=[0,0],f=1.3;return d.selectChart.select("svg").selectAll(".dummy").data([a,b]).enter().append("text").text(function(a){return d.dataLabelFormat(a.id)(a)}).each(function(a,b){e[b]=this.getBoundingClientRect()[c]*f}).remove(),e},i.isNoneArc=function(a){return this.hasTarget(this.data.targets,a.id)},i.isArc=function(a){return"data"in a&&this.hasTarget(this.data.targets,a.data.id)},i.findSameXOfValues=function(a,b){var c,d=a[b].x,e=[];for(c=b-1;c>=0&&d===a[c].x;c--)e.push(a[c]);for(c=b;cf&&(e=f,c=a)}),c},i.dist=function(a,b){var c=this,d=c.config,e=d.axis_rotated?1:0,f=d.axis_rotated?0:1,g=c.circleY(a,a.index),h=c.x(a.x);return Math.sqrt(Math.pow(h-b[e],2)+Math.pow(g-b[f],2))},i.convertValuesToStep=function(a){var b,c=[].concat(a);if(!this.isCategorized())return a;for(b=a.length+1;b>0;b--)c[b]=c[b-1];return c[0]={x:c[0].x-1,value:c[0].value,id:c[0].id},c[a.length+1]={x:c[a.length].x+1,value:c[a.length].value,id:c[a.length].id},c},i.updateDataAttributes=function(a,b){var c=this,d=c.config,e=d["data_"+a];return"undefined"==typeof b?e:(Object.keys(b).forEach(function(a){e[a]=b[a]}),c.redraw({withLegend:!0}),e)},i.convertUrlToData=function(a,b,c,d,e){var f=this,g=b?b:"csv",h=f.d3.xhr(a);c&&Object.keys(c).forEach(function(a){h.header(a,c[a])}),h.get(function(a,b){var c;if(!b)throw new Error(a.responseURL+" "+a.status+" ("+a.statusText+")");c="json"===g?f.convertJsonToData(JSON.parse(b.response),d):"tsv"===g?f.convertTsvToData(b.response):f.convertCsvToData(b.response),e.call(f,c)})},i.convertXsvToData=function(a,b){var c,d=b.parseRows(a);return 1===d.length?(c=[{}],d[0].forEach(function(a){c[0][a]=null})):c=b.parse(a),c},i.convertCsvToData=function(a){return this.convertXsvToData(a,this.d3.csv)},i.convertTsvToData=function(a){return this.convertXsvToData(a,this.d3.tsv)},i.convertJsonToData=function(a,b){var c,d,e=this,f=[];return b?(b.x?(c=b.value.concat(b.x),e.config.data_x=b.x):c=b.value,f.push(c),a.forEach(function(a){var b=[];c.forEach(function(c){var d=p(a[c])?null:a[c];b.push(d)}),f.push(b)}),d=e.convertRowsToData(f)):(Object.keys(a).forEach(function(b){f.push([b].concat(a[b]))}),d=e.convertColumnsToData(f)),d},i.convertRowsToData=function(a){var b,c,d=a[0],e={},f=[];for(b=1;b=0?d.data.xs[c]=(b&&d.data.xs[c]?d.data.xs[c]:[]).concat(a.map(function(a){return a[f]}).filter(m).map(function(a,b){return d.generateTargetX(a,c,b)})):e.data_x?d.data.xs[c]=d.getOtherTargetXs():v(e.data_xs)&&(d.data.xs[c]=d.getXValuesOfXKey(f,d.data.targets)):d.data.xs[c]=a.map(function(a,b){return b})}),f.forEach(function(a){if(!d.data.xs[a])throw new Error('x is not defined for id = "'+a+'".')}),c=f.map(function(b,c){var f=e.data_idConverter(b);return{id:f,id_org:b,values:a.map(function(a,g){var h=d.getXKey(b),i=a[h],j=d.generateTargetX(i,b,g),k=null===a[b]||isNaN(a[b])?null:+a[b];return d.isCustomX()&&d.isCategorized()&&0===c&&i&&(0===g&&(e.axis_x_categories=[]),e.axis_x_categories.push(i)),(p(a[b])||d.data.xs[b].length<=g)&&(j=void 0),{x:j,value:k,id:f}}).filter(function(a){return q(a.x)})}}),c.forEach(function(a){var b;e.data_xSort&&(a.values=a.values.sort(function(a,b){var c=a.x||0===a.x?a.x:1/0,d=b.x||0===b.x?b.x:1/0;return c-d})),b=0,a.values.forEach(function(a){a.index=b++}),d.data.xs[a.id].sort(function(a,b){return a-b})}),d.hasNegativeValue=d.hasNegativeValueInTargets(c),d.hasPositiveValue=d.hasPositiveValueInTargets(c),e.data_type&&d.setTargetType(d.mapToIds(c).filter(function(a){return!(a in e.data_types)}),e.data_type),c.forEach(function(a){d.addCache(a.id_org,a)}),c},i.load=function(a,b){var c=this;a&&(b.filter&&(a=a.filter(b.filter)),(b.type||b.types)&&a.forEach(function(a){var d=b.types&&b.types[a.id]?b.types[a.id]:b.type;c.setTargetType(a.id,d)}),c.data.targets.forEach(function(b){for(var c=0;c0?c:320/(a.hasType("gauge")?2:1)},i.getCurrentPaddingTop=function(){var a=this,b=a.config,c=m(b.padding_top)?b.padding_top:0;return a.title&&a.title.node()&&(c+=a.getTitlePadding()),c},i.getCurrentPaddingBottom=function(){var a=this.config;return m(a.padding_bottom)?a.padding_bottom:0},i.getCurrentPaddingLeft=function(a){var b=this,c=b.config;return m(c.padding_left)?c.padding_left:c.axis_rotated?c.axis_x_show?Math.max(r(b.getAxisWidthByAxisId("x",a)),40):1:!c.axis_y_show||c.axis_y_inner?b.axis.getYAxisLabelPosition().isOuter?30:1:r(b.getAxisWidthByAxisId("y",a))},i.getCurrentPaddingRight=function(){var a=this,b=a.config,c=10,d=a.isLegendRight?a.getLegendWidth()+20:0;return m(b.padding_right)?b.padding_right+1:b.axis_rotated?c+d:!b.axis_y2_show||b.axis_y2_inner?2+d+(a.axis.getY2AxisLabelPosition().isOuter?20:0):r(a.getAxisWidthByAxisId("y2"))+d},i.getParentRectValue=function(a){for(var b,c=this.selectChart.node();c&&"BODY"!==c.tagName;){try{b=c.getBoundingClientRect()[a]}catch(d){"width"===a&&(b=c.offsetWidth)}if(b)break;c=c.parentNode}return b},i.getParentWidth=function(){return this.getParentRectValue("width")},i.getParentHeight=function(){var a=this.selectChart.style("height");return a.indexOf("px")>0?+a.replace("px",""):0},i.getSvgLeft=function(a){var b=this,c=b.config,d=c.axis_rotated||!c.axis_rotated&&!c.axis_y_inner,e=c.axis_rotated?l.axisX:l.axisY,f=b.main.select("."+e).node(),g=f&&d?f.getBoundingClientRect():{right:0},h=b.selectChart.node().getBoundingClientRect(),i=b.hasArcType(),j=g.right-h.left-(i?0:b.getCurrentPaddingLeft(a));return j>0?j:0},i.getAxisWidthByAxisId=function(a,b){var c=this,d=c.axis.getLabelPositionById(a);return c.axis.getMaxTickWidth(a,b)+(d.isInner?20:40)},i.getHorizontalAxisHeight=function(a){var b=this,c=b.config,d=30;return"x"!==a||c.axis_x_show?"x"===a&&c.axis_x_height?c.axis_x_height:"y"!==a||c.axis_y_show?"y2"!==a||c.axis_y2_show?("x"===a&&!c.axis_rotated&&c.axis_x_tick_rotate&&(d=30+b.axis.getMaxTickWidth(a)*Math.cos(Math.PI*(90-c.axis_x_tick_rotate)/180)),"y"===a&&c.axis_rotated&&c.axis_y_tick_rotate&&(d=30+b.axis.getMaxTickWidth(a)*Math.cos(Math.PI*(90-c.axis_y_tick_rotate)/180)),d+(b.axis.getLabelPositionById(a).isInner?0:10)+("y2"===a?-10:0)):b.rotated_padding_top:!c.legend_show||b.isLegendRight||b.isLegendInset?1:10:8},i.getEventRectWidth=function(){return Math.max(0,this.xAxis.tickInterval())},i.getShapeIndices=function(a){var b,c,d=this,e=d.config,f={},g=0;return d.filterTargetsToShow(d.data.targets.filter(a,d)).forEach(function(a){for(b=0;b=0&&(j+=h(e[g].value)-i))}),j}},i.isWithinShape=function(a,b){var c,d=this,e=d.d3.select(a);return d.isTargetToShow(b.id)?"circle"===a.nodeName?c=d.isStepType(b)?d.isWithinStep(a,d.getYScale(b.id)(b.value)):d.isWithinCircle(a,1.5*d.pointSelectR(b)):"path"===a.nodeName&&(c=e.classed(l.bar)?d.isWithinBar(a):!0):c=!1,c},i.getInterpolate=function(a){var b=this,c=b.isInterpolationType(b.config.spline_interpolation_type)?b.config.spline_interpolation_type:"cardinal";return b.isSplineType(a)?c:b.isStepType(a)?b.config.line_step_type:"linear"},i.initLine=function(){var a=this;a.main.select("."+l.chart).append("g").attr("class",l.chartLines)},i.updateTargetsForLine=function(a){var b,c,d=this,e=d.config,f=d.classChartLine.bind(d),g=d.classLines.bind(d),h=d.classAreas.bind(d),i=d.classCircles.bind(d),j=d.classFocus.bind(d);b=d.main.select("."+l.chartLines).selectAll("."+l.chartLine).data(a).attr("class",function(a){return f(a)+j(a)}),c=b.enter().append("g").attr("class",f).style("opacity",0).style("pointer-events","none"),c.append("g").attr("class",g),c.append("g").attr("class",h),c.append("g").attr("class",function(a){return d.generateClass(l.selectedCircles,a.id)}),c.append("g").attr("class",i).style("cursor",function(a){return e.data_selection_isselectable(a)?"pointer":null}),a.forEach(function(a){d.main.selectAll("."+l.selectedCircles+d.getTargetSelectorSuffix(a.id)).selectAll("."+l.selectedCircle).each(function(b){b.value=a.values[b.index].value})})},i.updateLine=function(a){var b=this;b.mainLine=b.main.selectAll("."+l.lines).selectAll("."+l.line).data(b.lineData.bind(b)),b.mainLine.enter().append("path").attr("class",b.classLine.bind(b)).style("stroke",b.color),b.mainLine.style("opacity",b.initialOpacity.bind(b)).style("shape-rendering",function(a){return b.isStepType(a)?"crispEdges":""}).attr("transform",null),b.mainLine.exit().transition().duration(a).style("opacity",0).remove()},i.redrawLine=function(a,b){return[(b?this.mainLine.transition(Math.random().toString()):this.mainLine).attr("d",a).style("stroke",this.color).style("opacity",1)]},i.generateDrawLine=function(a,b){var c=this,d=c.config,e=c.d3.svg.line(),f=c.generateGetLinePoints(a,b),g=b?c.getSubYScale:c.getYScale,h=function(a){return(b?c.subxx:c.xx).call(c,a)},i=function(a,b){return d.data_groups.length>0?f(a,b)[0][1]:g.call(c,a.id)(a.value)};return e=d.axis_rotated?e.x(i).y(h):e.x(h).y(i),d.line_connectNull||(e=e.defined(function(a){return null!=a.value})),function(a){var f,h=d.line_connectNull?c.filterRemoveNull(a.values):a.values,i=b?c.x:c.subX,j=g.call(c,a.id),k=0,l=0;return c.isLineType(a)?d.data_regions[a.id]?f=c.lineWithRegions(h,i,j,d.data_regions[a.id]):(c.isStepType(a)&&(h=c.convertValuesToStep(h)),f=e.interpolate(c.getInterpolate(a))(h)):(h[0]&&(k=i(h[0].x),l=j(h[0].value)),f=d.axis_rotated?"M "+l+" "+k:"M "+k+" "+l),f?f:"M 0 0"}},i.generateGetLinePoints=function(a,b){var c=this,d=c.config,e=a.__max__+1,f=c.getShapeX(0,e,a,!!b),g=c.getShapeY(!!b),h=c.getShapeOffset(c.isLineType,a,!!b),i=b?c.getSubYScale:c.getYScale;return function(a,b){var e=i.call(c,a.id)(0),j=h(a,b)||e,k=f(a),l=g(a);return d.axis_rotated&&(0l||a.value<0&&l>e)&&(l=e),[[k,l-(e-j)],[k,l-(e-j)],[k,l-(e-j)],[k,l-(e-j)]]}},i.lineWithRegions=function(a,b,c,d){function e(a,b){var c;for(c=0;c=h;h+=r)x+=i(a[g-1],a[g],h,o);w=a[g].x}return x},i.updateArea=function(a){var b=this,c=b.d3;b.mainArea=b.main.selectAll("."+l.areas).selectAll("."+l.area).data(b.lineData.bind(b)),b.mainArea.enter().append("path").attr("class",b.classArea.bind(b)).style("fill",b.color).style("opacity",function(){return b.orgAreaOpacity=+c.select(this).style("opacity"),0}),b.mainArea.style("opacity",b.orgAreaOpacity),b.mainArea.exit().transition().duration(a).style("opacity",0).remove()},i.redrawArea=function(a,b){return[(b?this.mainArea.transition(Math.random().toString()):this.mainArea).attr("d",a).style("fill",this.color).style("opacity",this.orgAreaOpacity)]},i.generateDrawArea=function(a,b){var c=this,d=c.config,e=c.d3.svg.area(),f=c.generateGetAreaPoints(a,b),g=b?c.getSubYScale:c.getYScale,h=function(a){return(b?c.subxx:c.xx).call(c,a)},i=function(a,b){return d.data_groups.length>0?f(a,b)[0][1]:g.call(c,a.id)(c.getAreaBaseValue(a.id))},j=function(a,b){return d.data_groups.length>0?f(a,b)[1][1]:g.call(c,a.id)(a.value)};return e=d.axis_rotated?e.x0(i).x1(j).y(h):e.x(h).y0(i).y1(j),d.line_connectNull||(e=e.defined(function(a){return null!==a.value})),function(a){var b,f=d.line_connectNull?c.filterRemoveNull(a.values):a.values,g=0,h=0;return c.isAreaType(a)?(c.isStepType(a)&&(f=c.convertValuesToStep(f)),b=e.interpolate(c.getInterpolate(a))(f)):(f[0]&&(g=c.x(f[0].x),h=c.getYScale(a.id)(f[0].value)),b=d.axis_rotated?"M "+h+" "+g:"M "+g+" "+h),b?b:"M 0 0"}},i.getAreaBaseValue=function(){return 0},i.generateGetAreaPoints=function(a,b){var c=this,d=c.config,e=a.__max__+1,f=c.getShapeX(0,e,a,!!b),g=c.getShapeY(!!b),h=c.getShapeOffset(c.isAreaType,a,!!b),i=b?c.getSubYScale:c.getYScale;return function(a,b){var e=i.call(c,a.id)(0),j=h(a,b)||e,k=f(a),l=g(a);return d.axis_rotated&&(0l||a.value<0&&l>e)&&(l=e),[[k,j],[k,l-(e-j)],[k,l-(e-j)],[k,j]]}},i.updateCircle=function(){var a=this;a.mainCircle=a.main.selectAll("."+l.circles).selectAll("."+l.circle).data(a.lineOrScatterData.bind(a)),a.mainCircle.enter().append("circle").attr("class",a.classCircle.bind(a)).attr("r",a.pointR.bind(a)).style("fill",a.color),a.mainCircle.style("opacity",a.initialOpacityForCircle.bind(a)),a.mainCircle.exit().remove()},i.redrawCircle=function(a,b,c){var d=this.main.selectAll("."+l.selectedCircle);return[(c?this.mainCircle.transition(Math.random().toString()):this.mainCircle).style("opacity",this.opacityForCircle.bind(this)).style("fill",this.color).attr("cx",a).attr("cy",b),(c?d.transition(Math.random().toString()):d).attr("cx",a).attr("cy",b)]},i.circleX=function(a){return a.x||0===a.x?this.x(a.x):null},i.updateCircleY=function(){var a,b,c=this;c.config.data_groups.length>0?(a=c.getShapeIndices(c.isLineType),b=c.generateGetLinePoints(a),c.circleY=function(a,c){return b(a,c)[0][1]}):c.circleY=function(a){return c.getYScale(a.id)(a.value)}},i.getCircles=function(a,b){var c=this;return(b?c.main.selectAll("."+l.circles+c.getTargetSelectorSuffix(b)):c.main).selectAll("."+l.circle+(m(a)?"-"+a:""))},i.expandCircles=function(a,b,c){var d=this,e=d.pointExpandedR.bind(d);c&&d.unexpandCircles(),d.getCircles(a,b).classed(l.EXPANDED,!0).attr("r",e)},i.unexpandCircles=function(a){var b=this,c=b.pointR.bind(b);b.getCircles(a).filter(function(){return b.d3.select(this).classed(l.EXPANDED)}).classed(l.EXPANDED,!1).attr("r",c)},i.pointR=function(a){var b=this,c=b.config;return b.isStepType(a)?0:n(c.point_r)?c.point_r(a):c.point_r},i.pointExpandedR=function(a){var b=this,c=b.config;return c.point_focus_expand_enabled?c.point_focus_expand_r?c.point_focus_expand_r:1.75*b.pointR(a):b.pointR(a)},i.pointSelectR=function(a){var b=this,c=b.config;return c.point_select_r?c.point_select_r:4*b.pointR(a)},i.isWithinCircle=function(a,b){var c=this.d3,d=c.mouse(a),e=c.select(a),f=+e.attr("cx"),g=+e.attr("cy");return Math.sqrt(Math.pow(f-d[0],2)+Math.pow(g-d[1],2))d.bar_width_max?d.bar_width_max:e},i.getBars=function(a,b){var c=this;return(b?c.main.selectAll("."+l.bars+c.getTargetSelectorSuffix(b)):c.main).selectAll("."+l.bar+(m(a)?"-"+a:""))},i.expandBars=function(a,b,c){var d=this;c&&d.unexpandBars(),d.getBars(a,b).classed(l.EXPANDED,!0)},i.unexpandBars=function(a){var b=this;b.getBars(a).classed(l.EXPANDED,!1)},i.generateDrawBar=function(a,b){var c=this,d=c.config,e=c.generateGetBarPoints(a,b);return function(a,b){var c=e(a,b),f=d.axis_rotated?1:0,g=d.axis_rotated?0:1,h="M "+c[0][f]+","+c[0][g]+" L"+c[1][f]+","+c[1][g]+" L"+c[2][f]+","+c[2][g]+" L"+c[3][f]+","+c[3][g]+" z";return h}},i.generateGetBarPoints=function(a,b){var c=this,d=b?c.subXAxis:c.xAxis,e=a.__max__+1,f=c.getBarW(d,e),g=c.getShapeX(f,e,a,!!b),h=c.getShapeY(!!b),i=c.getShapeOffset(c.isBarType,a,!!b),j=b?c.getSubYScale:c.getYScale;return function(a,b){var d=j.call(c,a.id)(0),e=i(a,b)||d,k=g(a),l=h(a);return c.config.axis_rotated&&(0l||a.value<0&&l>d)&&(l=d),[[k,e],[k,l-(d-e)],[k+f,l-(d-e)],[k+f,e]]}},i.isWithinBar=function(a){var b=this.d3.mouse(a),c=a.getBoundingClientRect(),d=a.pathSegList.getItem(0),e=a.pathSegList.getItem(1),f=Math.min(d.x,e.x),g=Math.min(d.y,e.y),h=c.width,i=c.height,j=2,k=f-j,l=f+h+j,m=g+i+j,n=g-j;return kf.width?d=f.width-g.width:0>d&&(d=4)),d},i.getYForText=function(a,b,c){var d,e=this,f=c.getBoundingClientRect();return e.config.axis_rotated?d=(a[0][0]+a[2][0]+.6*f.height)/2:(d=a[2][1],b.value<0||0===b.value&&!e.hasPositiveValue?(d+=f.height,e.isBarType(b)&&e.isSafari()?d-=3:!e.isBarType(b)&&e.isChrome()&&(d+=3)):d+=e.isBarType(b)?-3:-6),null!==b.value||e.config.axis_rotated||(dthis.height&&(d=this.height-4)),d},i.setTargetType=function(a,b){var c=this,d=c.config;c.mapToTargetIds(a).forEach(function(a){c.withoutFadeIn[a]=b===d.data_types[a],d.data_types[a]=b}),a||(d.data_type=b)},i.hasType=function(a,b){var c=this,d=c.config.data_types,e=!1;return b=b||c.data.targets,b&&b.length?b.forEach(function(b){var c=d[b.id];(c&&c.indexOf(a)>=0||!c&&"line"===a)&&(e=!0)}):Object.keys(d).length?Object.keys(d).forEach(function(b){d[b]===a&&(e=!0)}):e=c.config.data_type===a,e},i.hasArcType=function(a){return this.hasType("pie",a)||this.hasType("donut",a)||this.hasType("gauge",a)},i.isLineType=function(a){var b=this.config,c=o(a)?a:a.id;return!b.data_types[c]||["line","spline","area","area-spline","step","area-step"].indexOf(b.data_types[c])>=0},i.isStepType=function(a){var b=o(a)?a:a.id;return["step","area-step"].indexOf(this.config.data_types[b])>=0},i.isSplineType=function(a){var b=o(a)?a:a.id;return["spline","area-spline"].indexOf(this.config.data_types[b])>=0},i.isAreaType=function(a){var b=o(a)?a:a.id;return["area","area-spline","area-step"].indexOf(this.config.data_types[b])>=0},i.isBarType=function(a){var b=o(a)?a:a.id;return"bar"===this.config.data_types[b]},i.isScatterType=function(a){var b=o(a)?a:a.id;return"scatter"===this.config.data_types[b]},i.isPieType=function(a){var b=o(a)?a:a.id;return"pie"===this.config.data_types[b]},i.isGaugeType=function(a){var b=o(a)?a:a.id;return"gauge"===this.config.data_types[b]},i.isDonutType=function(a){var b=o(a)?a:a.id;return"donut"===this.config.data_types[b]},i.isArcType=function(a){return this.isPieType(a)||this.isDonutType(a)||this.isGaugeType(a)},i.lineData=function(a){return this.isLineType(a)?[a]:[]},i.arcData=function(a){return this.isArcType(a.data)?[a]:[]},i.barData=function(a){return this.isBarType(a)?a.values:[]},i.lineOrScatterData=function(a){return this.isLineType(a)||this.isScatterType(a)?a.values:[]},i.barOrLineData=function(a){return this.isBarType(a)||this.isLineType(a)?a.values:[]},i.isInterpolationType=function(a){return["linear","linear-closed","basis","basis-open","basis-closed","bundle","cardinal","cardinal-open","cardinal-closed","monotone"].indexOf(a)>=0},i.initGrid=function(){var a=this,b=a.config,c=a.d3;a.grid=a.main.append("g").attr("clip-path",a.clipPathForGrid).attr("class",l.grid),b.grid_x_show&&a.grid.append("g").attr("class",l.xgrids),b.grid_y_show&&a.grid.append("g").attr("class",l.ygrids),b.grid_focus_show&&a.grid.append("g").attr("class",l.xgridFocus).append("line").attr("class",l.xgridFocus),a.xgrid=c.selectAll([]),b.grid_lines_front||a.initGridLines()},i.initGridLines=function(){var a=this,b=a.d3;a.gridLines=a.main.append("g").attr("clip-path",a.clipPathForGrid).attr("class",l.grid+" "+l.gridLines),a.gridLines.append("g").attr("class",l.xgridLines),a.gridLines.append("g").attr("class",l.ygridLines),a.xgridLines=b.selectAll([])},i.updateXGrid=function(a){var b=this,c=b.config,d=b.d3,e=b.generateGridData(c.grid_x_type,b.x),f=b.isCategorized()?b.xAxis.tickOffset():0;b.xgridAttr=c.axis_rotated?{x1:0,x2:b.width,y1:function(a){return b.x(a)-f},y2:function(a){return b.x(a)-f}}:{x1:function(a){return b.x(a)+f},x2:function(a){return b.x(a)+f},y1:0,y2:b.height},b.xgrid=b.main.select("."+l.xgrids).selectAll("."+l.xgrid).data(e),b.xgrid.enter().append("line").attr("class",l.xgrid),a||b.xgrid.attr(b.xgridAttr).style("opacity",function(){return+d.select(this).attr(c.axis_rotated?"y1":"x1")===(c.axis_rotated?b.height:0)?0:1}),b.xgrid.exit().remove()},i.updateYGrid=function(){var a=this,b=a.config,c=a.yAxis.tickValues()||a.y.ticks(b.grid_y_ticks);a.ygrid=a.main.select("."+l.ygrids).selectAll("."+l.ygrid).data(c),a.ygrid.enter().append("line").attr("class",l.ygrid),a.ygrid.attr("x1",b.axis_rotated?a.y:0).attr("x2",b.axis_rotated?a.y:a.width).attr("y1",b.axis_rotated?0:a.y).attr("y2",b.axis_rotated?a.height:a.y),a.ygrid.exit().remove(),a.smoothLines(a.ygrid,"grid")},i.gridTextAnchor=function(a){return a.position?a.position:"end"},i.gridTextDx=function(a){return"start"===a.position?4:"middle"===a.position?0:-4},i.xGridTextX=function(a){return"start"===a.position?-this.height:"middle"===a.position?-this.height/2:0},i.yGridTextX=function(a){return"start"===a.position?0:"middle"===a.position?this.width/2:this.width},i.updateGrid=function(a){var b,c,d,e=this,f=e.main,g=e.config;e.grid.style("visibility",e.hasArcType()?"hidden":"visible"),f.select("line."+l.xgridFocus).style("visibility","hidden"),g.grid_x_show&&e.updateXGrid(),e.xgridLines=f.select("."+l.xgridLines).selectAll("."+l.xgridLine).data(g.grid_x_lines),b=e.xgridLines.enter().append("g").attr("class",function(a){return l.xgridLine+(a["class"]?" "+a["class"]:"")}),b.append("line").style("opacity",0),b.append("text").attr("text-anchor",e.gridTextAnchor).attr("transform",g.axis_rotated?"":"rotate(-90)").attr("dx",e.gridTextDx).attr("dy",-5).style("opacity",0),e.xgridLines.exit().transition().duration(a).style("opacity",0).remove(),g.grid_y_show&&e.updateYGrid(),e.ygridLines=f.select("."+l.ygridLines).selectAll("."+l.ygridLine).data(g.grid_y_lines),c=e.ygridLines.enter().append("g").attr("class",function(a){return l.ygridLine+(a["class"]?" "+a["class"]:"")}),c.append("line").style("opacity",0),c.append("text").attr("text-anchor",e.gridTextAnchor).attr("transform",g.axis_rotated?"rotate(-90)":"").attr("dx",e.gridTextDx).attr("dy",-5).style("opacity",0),d=e.yv.bind(e),e.ygridLines.select("line").transition().duration(a).attr("x1",g.axis_rotated?d:0).attr("x2",g.axis_rotated?d:e.width).attr("y1",g.axis_rotated?0:d).attr("y2",g.axis_rotated?e.height:d).style("opacity",1),e.ygridLines.select("text").transition().duration(a).attr("x",g.axis_rotated?e.xGridTextX.bind(e):e.yGridTextX.bind(e)).attr("y",d).text(function(a){return a.text}).style("opacity",1),e.ygridLines.exit().transition().duration(a).style("opacity",0).remove()},i.redrawGrid=function(a){var b=this,c=b.config,d=b.xv.bind(b),e=b.xgridLines.select("line"),f=b.xgridLines.select("text");return[(a?e.transition():e).attr("x1",c.axis_rotated?0:d).attr("x2",c.axis_rotated?b.width:d).attr("y1",c.axis_rotated?d:0).attr("y2",c.axis_rotated?d:b.height).style("opacity",1),(a?f.transition():f).attr("x",c.axis_rotated?b.yGridTextX.bind(b):b.xGridTextX.bind(b)).attr("y",d).text(function(a){return a.text}).style("opacity",1)]},i.showXGridFocus=function(a){var b=this,c=b.config,d=a.filter(function(a){return a&&m(a.value)}),e=b.main.selectAll("line."+l.xgridFocus),f=b.xx.bind(b);c.tooltip_show&&(b.hasType("scatter")||b.hasArcType()||(e.style("visibility","visible").data([d[0]]).attr(c.axis_rotated?"y1":"x1",f).attr(c.axis_rotated?"y2":"x2",f),b.smoothLines(e,"grid")))},i.hideXGridFocus=function(){this.main.select("line."+l.xgridFocus).style("visibility","hidden")},i.updateXgridFocus=function(){var a=this,b=a.config;a.main.select("line."+l.xgridFocus).attr("x1",b.axis_rotated?0:-10).attr("x2",b.axis_rotated?a.width:-10).attr("y1",b.axis_rotated?-10:0).attr("y2",b.axis_rotated?-10:a.height)},i.generateGridData=function(a,b){var c,d,e,f,g=this,h=[],i=g.main.select("."+l.axisX).selectAll(".tick").size();if("year"===a)for(c=g.getXDomain(),d=c[0].getFullYear(),e=c[1].getFullYear(),f=d;e>=f;f++)h.push(new Date(f+"-01-01 00:00:00"));else h=b.ticks(10),h.length>i&&(h=h.filter(function(a){return(""+a).indexOf(".")<0}));return h},i.getGridFilterToRemove=function(a){return a?function(b){var c=!1;return[].concat(a).forEach(function(a){("value"in a&&b.value===a.value||"class"in a&&b["class"]===a["class"])&&(c=!0)}),c}:function(){return!0}},i.removeGridLines=function(a,b){var c=this,d=c.config,e=c.getGridFilterToRemove(a),f=function(a){return!e(a)},g=b?l.xgridLines:l.ygridLines,h=b?l.xgridLine:l.ygridLine;c.main.select("."+g).selectAll("."+h).filter(e).transition().duration(d.transition_duration).style("opacity",0).remove(),b?d.grid_x_lines=d.grid_x_lines.filter(f):d.grid_y_lines=d.grid_y_lines.filter(f)},i.initTooltip=function(){var a,b=this,c=b.config;if(b.tooltip=b.selectChart.style("position","relative").append("div").attr("class",l.tooltipContainer).style("position","absolute").style("pointer-events","none").style("display","none"),c.tooltip_init_show){if(b.isTimeSeries()&&o(c.tooltip_init_x)){for(c.tooltip_init_x=b.parseDate(c.tooltip_init_x),a=0;a0&&b.value>0?p?q.indexOf(a.id)-q.indexOf(b.id):q.indexOf(b.id)-q.indexOf(a.id):p?a.value-b.value:b.value-a.value})}for(f=0;f"+(g||0===g?""+g+"":"")),h=o(a[f].value,a[f].ratio,a[f].id,a[f].index),void 0!==h)){if(null===a[f].name)continue;i=n(a[f].name,a[f].ratio,a[f].id,a[f].index),j=k.levelColor?k.levelColor(a[f].value):d(a[f].id),e+="",e+=""+i+"",e+=""+h+"",e+=""}return e+""},i.tooltipPosition=function(a,b,c,d){var e,f,g,h,i,j=this,k=j.config,l=j.d3,m=j.hasArcType(),n=l.mouse(d);return m?(f=(j.width-(j.isLegendRight?j.getLegendWidth():0))/2+n[0],h=j.height/2+n[1]+20):(e=j.getSvgLeft(!0),k.axis_rotated?(f=e+n[0]+100,g=f+b,i=j.currentWidth-j.getCurrentPaddingRight(),h=j.x(a[0].x)+20):(f=e+j.getCurrentPaddingLeft(!0)+j.x(a[0].x)+20,g=f+b,i=e+j.currentWidth-j.getCurrentPaddingRight(),h=n[1]+15),g>i&&(f-=g-i+20),h+c>j.currentHeight&&(h-=c+30)),0>h&&(h=0),{top:h,left:f}},i.showTooltip=function(a,b){var c,d,e,f=this,g=f.config,h=f.hasArcType(),j=a.filter(function(a){return a&&m(a.value)}),k=g.tooltip_position||i.tooltipPosition;0!==j.length&&g.tooltip_show&&(f.tooltip.html(g.tooltip_contents.call(f,a,f.axis.getXAxisTickFormat(),f.getYFormat(h),f.color)).style("display","block"),c=f.tooltip.property("offsetWidth"),d=f.tooltip.property("offsetHeight"),e=k.call(this,j,c,d,b),f.tooltip.style("top",e.top+"px").style("left",e.left+"px"))},i.hideTooltip=function(){this.tooltip.style("display","none")},i.initLegend=function(){var a=this;return a.legendItemTextBox={},a.legendHasRendered=!1,a.legend=a.svg.append("g").attr("transform",a.getTranslate("legend")),a.config.legend_show?void a.updateLegendWithDefaults():(a.legend.style("visibility","hidden"),void(a.hiddenLegendIds=a.mapToIds(a.data.targets)))},i.updateLegendWithDefaults=function(){var a=this;a.updateLegend(a.mapToIds(a.data.targets),{withTransform:!1,withTransitionForTransform:!1,withTransition:!1})},i.updateSizeForLegend=function(a,b){var c=this,d=c.config,e={top:c.isLegendTop?c.getCurrentPaddingTop()+d.legend_inset_y+5.5:c.currentHeight-a-c.getCurrentPaddingBottom()-d.legend_inset_y,left:c.isLegendLeft?c.getCurrentPaddingLeft()+d.legend_inset_x+.5:c.currentWidth-b-c.getCurrentPaddingRight()-d.legend_inset_x+.5};c.margin3={top:c.isLegendRight?0:c.isLegendInset?e.top:c.currentHeight-a,right:NaN,bottom:0,left:c.isLegendRight?c.currentWidth-b:c.isLegendInset?e.left:0}},i.transformLegend=function(a){var b=this;(a?b.legend.transition():b.legend).attr("transform",b.getTranslate("legend"))},i.updateLegendStep=function(a){this.legendStep=a},i.updateLegendItemWidth=function(a){this.legendItemWidth=a},i.updateLegendItemHeight=function(a){this.legendItemHeight=a},i.getLegendWidth=function(){var a=this;return a.config.legend_show?a.isLegendRight||a.isLegendInset?a.legendItemWidth*(a.legendStep+1):a.currentWidth:0},i.getLegendHeight=function(){var a=this,b=0;return a.config.legend_show&&(b=a.isLegendRight?a.currentHeight:Math.max(20,a.legendItemHeight)*(a.legendStep+1)),b},i.opacityForLegend=function(a){return a.classed(l.legendItemHidden)?null:1},i.opacityForUnfocusedLegend=function(a){return a.classed(l.legendItemHidden)?null:.3},i.toggleFocusLegend=function(a,b){var c=this;a=c.mapToTargetIds(a),c.legend.selectAll("."+l.legendItem).filter(function(b){return a.indexOf(b)>=0}).classed(l.legendItemFocused,b).transition().duration(100).style("opacity",function(){var a=b?c.opacityForLegend:c.opacityForUnfocusedLegend;return a.call(c,c.d3.select(this))})},i.revertLegend=function(){var a=this,b=a.d3;a.legend.selectAll("."+l.legendItem).classed(l.legendItemFocused,!1).transition().duration(100).style("opacity",function(){return a.opacityForLegend(b.select(this))})},i.showLegend=function(a){var b=this,c=b.config;c.legend_show||(c.legend_show=!0,b.legend.style("visibility","visible"),b.legendHasRendered||b.updateLegendWithDefaults()),b.removeHiddenLegendIds(a),b.legend.selectAll(b.selectorLegends(a)).style("visibility","visible").transition().style("opacity",function(){return b.opacityForLegend(b.d3.select(this))})},i.hideLegend=function(a){var b=this,c=b.config;c.legend_show&&u(a)&&(c.legend_show=!1,b.legend.style("visibility","hidden")),b.addHiddenLegendIds(a),b.legend.selectAll(b.selectorLegends(a)).style("opacity",0).style("visibility","hidden")},i.clearLegendItemTextBoxCache=function(){this.legendItemTextBox={}},i.updateLegend=function(a,b,c){function d(a,b){return y.legendItemTextBox[b]||(y.legendItemTextBox[b]=y.getTextRect(a.textContent,l.legendItem,a)),y.legendItemTextBox[b]}function e(b,c,e){function f(a,b){b||(g=(o-G-n)/2,E>g&&(g=(o-n)/2,G=0,M++)),L[a]=M,K[M]=y.isLegendInset?10:g,H[a]=G,G+=n}var g,h,i=0===e,j=e===a.length-1,k=d(b,c),l=k.width+F+(!j||y.isLegendRight||y.isLegendInset?B:0)+z.legend_padding,m=k.height+A,n=y.isLegendRight||y.isLegendInset?m:l,o=y.isLegendRight||y.isLegendInset?y.getLegendHeight():y.getLegendWidth();return i&&(G=0,M=0,C=0,D=0),z.legend_show&&!y.isLegendToShow(c)?void(I[c]=J[c]=L[c]=H[c]=0):(I[c]=l,J[c]=m,(!C||l>=C)&&(C=l),(!D||m>=D)&&(D=m),h=y.isLegendRight||y.isLegendInset?D:C,void(z.legend_equally?(Object.keys(I).forEach(function(a){I[a]=C}),Object.keys(J).forEach(function(a){J[a]=D}),g=(o-h*a.length)/2,E>g?(G=0,M=0,a.forEach(function(a){f(a)})):f(c,!0)):f(c)))}var f,g,h,i,j,k,m,n,o,p,r,s,t,u,v,x,y=this,z=y.config,A=4,B=10,C=0,D=0,E=10,F=z.legend_item_tile_width+5,G=0,H={},I={},J={},K=[0],L={},M=0;a=a.filter(function(a){return!q(z.data_names[a])||null!==z.data_names[a]}),b=b||{},r=w(b,"withTransition",!0),s=w(b,"withTransitionForTransform",!0),y.isLegendInset&&(M=z.legend_inset_step?z.legend_inset_step:a.length,y.updateLegendStep(M)),y.isLegendRight?(f=function(a){return C*L[a]},i=function(a){return K[L[a]]+H[a]}):y.isLegendInset?(f=function(a){return C*L[a]+10},i=function(a){return K[L[a]]+H[a]}):(f=function(a){return K[L[a]]+H[a]},i=function(a){return D*L[a]}),g=function(a,b){return f(a,b)+4+z.legend_item_tile_width},j=function(a,b){return i(a,b)+9},h=function(a,b){return f(a,b)},k=function(a,b){return i(a,b)-5},m=function(a,b){return f(a,b)-2},n=function(a,b){return f(a,b)-2+z.legend_item_tile_width},o=function(a,b){return i(a,b)+4},p=y.legend.selectAll("."+l.legendItem).data(a).enter().append("g").attr("class",function(a){return y.generateClass(l.legendItem,a)}).style("visibility",function(a){return y.isLegendToShow(a)?"visible":"hidden"}).style("cursor","pointer").on("click",function(a){z.legend_item_onclick?z.legend_item_onclick.call(y,a):y.d3.event.altKey?(y.api.hide(),y.api.show(a)):(y.api.toggle(a),y.isTargetToShow(a)?y.api.focus(a):y.api.revert())}).on("mouseover",function(a){z.legend_item_onmouseover?z.legend_item_onmouseover.call(y,a):(y.d3.select(this).classed(l.legendItemFocused,!0),!y.transiting&&y.isTargetToShow(a)&&y.api.focus(a))}).on("mouseout",function(a){z.legend_item_onmouseout?z.legend_item_onmouseout.call(y,a):(y.d3.select(this).classed(l.legendItemFocused,!1),y.api.revert())}),p.append("text").text(function(a){return q(z.data_names[a])?z.data_names[a]:a}).each(function(a,b){e(this,a,b)}).style("pointer-events","none").attr("x",y.isLegendRight||y.isLegendInset?g:-200).attr("y",y.isLegendRight||y.isLegendInset?-200:j),p.append("rect").attr("class",l.legendItemEvent).style("fill-opacity",0).attr("x",y.isLegendRight||y.isLegendInset?h:-200).attr("y",y.isLegendRight||y.isLegendInset?-200:k),p.append("line").attr("class",l.legendItemTile).style("stroke",y.color).style("pointer-events","none").attr("x1",y.isLegendRight||y.isLegendInset?m:-200).attr("y1",y.isLegendRight||y.isLegendInset?-200:o).attr("x2",y.isLegendRight||y.isLegendInset?n:-200).attr("y2",y.isLegendRight||y.isLegendInset?-200:o).attr("stroke-width",z.legend_item_tile_height),x=y.legend.select("."+l.legendBackground+" rect"),y.isLegendInset&&C>0&&0===x.size()&&(x=y.legend.insert("g","."+l.legendItem).attr("class",l.legendBackground).append("rect")),t=y.legend.selectAll("text").data(a).text(function(a){return q(z.data_names[a])?z.data_names[a]:a}).each(function(a,b){e(this,a,b)}),(r?t.transition():t).attr("x",g).attr("y",j),u=y.legend.selectAll("rect."+l.legendItemEvent).data(a),(r?u.transition():u).attr("width",function(a){return I[a]}).attr("height",function(a){return J[a]}).attr("x",h).attr("y",k),v=y.legend.selectAll("line."+l.legendItemTile).data(a),(r?v.transition():v).style("stroke",y.color).attr("x1",m).attr("y1",o).attr("x2",n).attr("y2",o),x&&(r?x.transition():x).attr("height",y.getLegendHeight()-12).attr("width",C*(M+1)+10),y.legend.selectAll("."+l.legendItem).classed(l.legendItemHidden,function(a){return!y.isTargetToShow(a)}),y.updateLegendItemWidth(C),y.updateLegendItemHeight(D),y.updateLegendStep(M),y.updateSizes(),y.updateScales(),y.updateSvgSize(),y.transformAll(s,c),y.legendHasRendered=!0},i.initTitle=function(){var a=this;a.title=a.svg.append("text").text(a.config.title_text).attr("class",a.CLASS.title)},i.redrawTitle=function(){var a=this;a.title.attr("x",a.xForTitle.bind(a)).attr("y",a.yForTitle.bind(a))},i.xForTitle=function(){var a,b=this,c=b.config,d=c.title_position||"left";return a=d.indexOf("right")>=0?b.currentWidth-b.getTextRect(b.title.node().textContent,b.CLASS.title,b.title.node()).width-c.title_padding.right:d.indexOf("center")>=0?(b.currentWidth-b.getTextRect(b.title.node().textContent,b.CLASS.title,b.title.node()).width)/2:c.title_padding.left},i.yForTitle=function(){var a=this;return a.config.title_padding.top+a.getTextRect(a.title.node().textContent,a.CLASS.title,a.title.node()).height},i.getTitlePadding=function(){var a=this;return a.yForTitle()+a.config.title_padding.bottom},c(b,f),f.prototype.init=function(){var a=this.owner,b=a.config,c=a.main;a.axes.x=c.append("g").attr("class",l.axis+" "+l.axisX).attr("clip-path",a.clipPathForXAxis).attr("transform",a.getTranslate("x")).style("visibility",b.axis_x_show?"visible":"hidden"),a.axes.x.append("text").attr("class",l.axisXLabel).attr("transform",b.axis_rotated?"rotate(-90)":"").style("text-anchor",this.textAnchorForXAxisLabel.bind(this)),a.axes.y=c.append("g").attr("class",l.axis+" "+l.axisY).attr("clip-path",b.axis_y_inner?"":a.clipPathForYAxis).attr("transform",a.getTranslate("y")).style("visibility",b.axis_y_show?"visible":"hidden"),a.axes.y.append("text").attr("class",l.axisYLabel).attr("transform",b.axis_rotated?"":"rotate(-90)").style("text-anchor",this.textAnchorForYAxisLabel.bind(this)),a.axes.y2=c.append("g").attr("class",l.axis+" "+l.axisY2).attr("transform",a.getTranslate("y2")).style("visibility",b.axis_y2_show?"visible":"hidden"),a.axes.y2.append("text").attr("class",l.axisY2Label).attr("transform",b.axis_rotated?"":"rotate(-90)").style("text-anchor",this.textAnchorForY2AxisLabel.bind(this))},f.prototype.getXAxis=function(a,b,c,d,e,f,h){var i=this.owner,j=i.config,k={isCategory:i.isCategorized(),withOuterTick:e,tickMultiline:j.axis_x_tick_multiline,tickWidth:j.axis_x_tick_width,tickTextRotate:h?0:j.axis_x_tick_rotate,withoutTransition:f},l=g(i.d3,k).scale(a).orient(b);return i.isTimeSeries()&&d&&"function"!=typeof d&&(d=d.map(function(a){return i.parseDate(a)})),l.tickFormat(c).tickValues(d),i.isCategorized()&&(l.tickCentered(j.axis_x_tick_centered),u(j.axis_x_tick_culling)&&(j.axis_x_tick_culling=!1)),l},f.prototype.updateXAxisTickValues=function(a,b){var c,d=this.owner,e=d.config;return(e.axis_x_tick_fit||e.axis_x_tick_count)&&(c=this.generateTickValues(d.mapTargetsToUniqueXs(a),e.axis_x_tick_count,d.isTimeSeries())),b?b.tickValues(c):(d.xAxis.tickValues(c),d.subXAxis.tickValues(c)),c},f.prototype.getYAxis=function(a,b,c,d,e,f,h){var i=this.owner,j=i.config,k={withOuterTick:e,withoutTransition:f,tickTextRotate:h?0:j.axis_y_tick_rotate},l=g(i.d3,k).scale(a).orient(b).tickFormat(c);return i.isTimeSeriesY()?l.ticks(i.d3.time[j.axis_y_tick_time_value],j.axis_y_tick_time_interval):l.tickValues(d),l},f.prototype.getId=function(a){var b=this.owner.config;return a in b.data_axes?b.data_axes[a]:"y"},f.prototype.getXAxisTickFormat=function(){var a=this.owner,b=a.config,c=a.isTimeSeries()?a.defaultAxisTimeFormat:a.isCategorized()?a.categoryName:function(a){return 0>a?a.toFixed(0):a};return b.axis_x_tick_format&&(n(b.axis_x_tick_format)?c=b.axis_x_tick_format:a.isTimeSeries()&&(c=function(c){return c?a.axisTimeFormat(b.axis_x_tick_format)(c):""})),n(c)?function(b){return c.call(a,b)}:c},f.prototype.getTickValues=function(a,b){return a?a:b?b.tickValues():void 0},f.prototype.getXAxisTickValues=function(){return this.getTickValues(this.owner.config.axis_x_tick_values,this.owner.xAxis)},f.prototype.getYAxisTickValues=function(){return this.getTickValues(this.owner.config.axis_y_tick_values,this.owner.yAxis)},f.prototype.getY2AxisTickValues=function(){return this.getTickValues(this.owner.config.axis_y2_tick_values,this.owner.y2Axis)},f.prototype.getLabelOptionByAxisId=function(a){var b,c=this.owner,d=c.config;return"y"===a?b=d.axis_y_label:"y2"===a?b=d.axis_y2_label:"x"===a&&(b=d.axis_x_label),b},f.prototype.getLabelText=function(a){var b=this.getLabelOptionByAxisId(a);return o(b)?b:b?b.text:null},f.prototype.setLabelText=function(a,b){var c=this.owner,d=c.config,e=this.getLabelOptionByAxisId(a);o(e)?"y"===a?d.axis_y_label=b:"y2"===a?d.axis_y2_label=b:"x"===a&&(d.axis_x_label=b):e&&(e.text=b)},f.prototype.getLabelPosition=function(a,b){var c=this.getLabelOptionByAxisId(a),d=c&&"object"==typeof c&&c.position?c.position:b;return{isInner:d.indexOf("inner")>=0,isOuter:d.indexOf("outer")>=0,isLeft:d.indexOf("left")>=0,isCenter:d.indexOf("center")>=0,isRight:d.indexOf("right")>=0,isTop:d.indexOf("top")>=0,isMiddle:d.indexOf("middle")>=0,isBottom:d.indexOf("bottom")>=0}},f.prototype.getXAxisLabelPosition=function(){return this.getLabelPosition("x",this.owner.config.axis_rotated?"inner-top":"inner-right")},f.prototype.getYAxisLabelPosition=function(){return this.getLabelPosition("y",this.owner.config.axis_rotated?"inner-right":"inner-top")},f.prototype.getY2AxisLabelPosition=function(){return this.getLabelPosition("y2",this.owner.config.axis_rotated?"inner-right":"inner-top")},f.prototype.getLabelPositionById=function(a){return"y2"===a?this.getY2AxisLabelPosition():"y"===a?this.getYAxisLabelPosition():this.getXAxisLabelPosition()},f.prototype.textForXAxisLabel=function(){return this.getLabelText("x")},f.prototype.textForYAxisLabel=function(){return this.getLabelText("y")},f.prototype.textForY2AxisLabel=function(){return this.getLabelText("y2")},f.prototype.xForAxisLabel=function(a,b){var c=this.owner;return a?b.isLeft?0:b.isCenter?c.width/2:c.width:b.isBottom?-c.height:b.isMiddle?-c.height/2:0},f.prototype.dxForAxisLabel=function(a,b){return a?b.isLeft?"0.5em":b.isRight?"-0.5em":"0":b.isTop?"-0.5em":b.isBottom?"0.5em":"0"},f.prototype.textAnchorForAxisLabel=function(a,b){return a?b.isLeft?"start":b.isCenter?"middle":"end":b.isBottom?"start":b.isMiddle?"middle":"end"},f.prototype.xForXAxisLabel=function(){return this.xForAxisLabel(!this.owner.config.axis_rotated,this.getXAxisLabelPosition())},f.prototype.xForYAxisLabel=function(){return this.xForAxisLabel(this.owner.config.axis_rotated,this.getYAxisLabelPosition())},f.prototype.xForY2AxisLabel=function(){return this.xForAxisLabel(this.owner.config.axis_rotated,this.getY2AxisLabelPosition())},f.prototype.dxForXAxisLabel=function(){return this.dxForAxisLabel(!this.owner.config.axis_rotated,this.getXAxisLabelPosition())},f.prototype.dxForYAxisLabel=function(){return this.dxForAxisLabel(this.owner.config.axis_rotated,this.getYAxisLabelPosition())},f.prototype.dxForY2AxisLabel=function(){return this.dxForAxisLabel(this.owner.config.axis_rotated,this.getY2AxisLabelPosition())},f.prototype.dyForXAxisLabel=function(){var a=this.owner,b=a.config,c=this.getXAxisLabelPosition();return b.axis_rotated?c.isInner?"1.2em":-25-this.getMaxTickWidth("x"):c.isInner?"-0.5em":b.axis_x_height?b.axis_x_height-10:"3em"},f.prototype.dyForYAxisLabel=function(){var a=this.owner,b=this.getYAxisLabelPosition();return a.config.axis_rotated?b.isInner?"-0.5em":"3em":b.isInner?"1.2em":-10-(a.config.axis_y_inner?0:this.getMaxTickWidth("y")+10)},f.prototype.dyForY2AxisLabel=function(){var a=this.owner,b=this.getY2AxisLabelPosition();return a.config.axis_rotated?b.isInner?"1.2em":"-2.2em":b.isInner?"-0.5em":15+(a.config.axis_y2_inner?0:this.getMaxTickWidth("y2")+15)},f.prototype.textAnchorForXAxisLabel=function(){var a=this.owner;return this.textAnchorForAxisLabel(!a.config.axis_rotated,this.getXAxisLabelPosition())},f.prototype.textAnchorForYAxisLabel=function(){var a=this.owner;return this.textAnchorForAxisLabel(a.config.axis_rotated,this.getYAxisLabelPosition())},f.prototype.textAnchorForY2AxisLabel=function(){var a=this.owner;return this.textAnchorForAxisLabel(a.config.axis_rotated,this.getY2AxisLabelPosition())},f.prototype.getMaxTickWidth=function(a,b){var c,d,e,f,g,h=this.owner,i=h.config,j=0;return b&&h.currentMaxTickWidths[a]?h.currentMaxTickWidths[a]:(h.svg&&(c=h.filterTargetsToShow(h.data.targets),"y"===a?(d=h.y.copy().domain(h.getYDomain(c,"y")),e=this.getYAxis(d,h.yOrient,i.axis_y_tick_format,h.yAxisTickValues,!1,!0,!0)):"y2"===a?(d=h.y2.copy().domain(h.getYDomain(c,"y2")),e=this.getYAxis(d,h.y2Orient,i.axis_y2_tick_format,h.y2AxisTickValues,!1,!0,!0)):(d=h.x.copy().domain(h.getXDomain(c)),e=this.getXAxis(d,h.xOrient,h.xAxisTickFormat,h.xAxisTickValues,!1,!0,!0),this.updateXAxisTickValues(c,e)),f=h.d3.select("body").append("div").classed("c3",!0),g=f.append("svg").style("visibility","hidden").style("position","fixed").style("top",0).style("left",0),g.append("g").call(e).each(function(){ -h.d3.select(this).selectAll("text").each(function(){var a=this.getBoundingClientRect();j=j?h.currentMaxTickWidths[a]:j,h.currentMaxTickWidths[a])},f.prototype.updateLabels=function(a){var b=this.owner,c=b.main.select("."+l.axisX+" ."+l.axisXLabel),d=b.main.select("."+l.axisY+" ."+l.axisYLabel),e=b.main.select("."+l.axisY2+" ."+l.axisY2Label);(a?c.transition():c).attr("x",this.xForXAxisLabel.bind(this)).attr("dx",this.dxForXAxisLabel.bind(this)).attr("dy",this.dyForXAxisLabel.bind(this)).text(this.textForXAxisLabel.bind(this)),(a?d.transition():d).attr("x",this.xForYAxisLabel.bind(this)).attr("dx",this.dxForYAxisLabel.bind(this)).attr("dy",this.dyForYAxisLabel.bind(this)).text(this.textForYAxisLabel.bind(this)),(a?e.transition():e).attr("x",this.xForY2AxisLabel.bind(this)).attr("dx",this.dxForY2AxisLabel.bind(this)).attr("dy",this.dyForY2AxisLabel.bind(this)).text(this.textForY2AxisLabel.bind(this))},f.prototype.getPadding=function(a,b,c,d){var e="number"==typeof a?a:a[b];return m(e)?"ratio"===a.unit?a[b]*d:this.convertPixelsToAxisPadding(e,d):c},f.prototype.convertPixelsToAxisPadding=function(a,b){var c=this.owner,d=c.config.axis_rotated?c.width:c.height;return b*(a/d)},f.prototype.generateTickValues=function(a,b,c){var d,e,f,g,h,i,j,k=a;if(b)if(d=n(b)?b():b,1===d)k=[a[0]];else if(2===d)k=[a[0],a[a.length-1]];else if(d>2){for(g=d-2,e=a[0],f=a[a.length-1],h=(f-e)/(g+1),k=[e],i=0;g>i;i++)j=+e+h*(i+1),k.push(c?new Date(j):j);k.push(f)}return c||(k=k.sort(function(a,b){return a-b})),k},f.prototype.generateTransitions=function(a){var b=this.owner,c=b.axes;return{axisX:a?c.x.transition().duration(a):c.x,axisY:a?c.y.transition().duration(a):c.y,axisY2:a?c.y2.transition().duration(a):c.y2,axisSubX:a?c.subx.transition().duration(a):c.subx}},f.prototype.redraw=function(a,b){var c=this.owner;c.axes.x.style("opacity",b?0:1),c.axes.y.style("opacity",b?0:1),c.axes.y2.style("opacity",b?0:1),c.axes.subx.style("opacity",b?0:1),a.axisX.call(c.xAxis),a.axisY.call(c.yAxis),a.axisY2.call(c.y2Axis),a.axisSubX.call(c.subXAxis)},i.getClipPath=function(b){var c=a.navigator.appVersion.toLowerCase().indexOf("msie 9.")>=0;return"url("+(c?"":document.URL.split("#")[0])+"#"+b+")"},i.appendClip=function(a,b){return a.append("clipPath").attr("id",b).append("rect")},i.getAxisClipX=function(a){var b=Math.max(30,this.margin.left);return a?-(1+b):-(b-1)},i.getAxisClipY=function(a){return a?-20:-this.margin.top},i.getXAxisClipX=function(){var a=this;return a.getAxisClipX(!a.config.axis_rotated)},i.getXAxisClipY=function(){var a=this;return a.getAxisClipY(!a.config.axis_rotated)},i.getYAxisClipX=function(){var a=this;return a.config.axis_y_inner?-1:a.getAxisClipX(a.config.axis_rotated)},i.getYAxisClipY=function(){var a=this;return a.getAxisClipY(a.config.axis_rotated)},i.getAxisClipWidth=function(a){var b=this,c=Math.max(30,b.margin.left),d=Math.max(30,b.margin.right);return a?b.width+2+c+d:b.margin.left+20},i.getAxisClipHeight=function(a){return(a?this.margin.bottom:this.margin.top+this.height)+20},i.getXAxisClipWidth=function(){var a=this;return a.getAxisClipWidth(!a.config.axis_rotated)},i.getXAxisClipHeight=function(){var a=this;return a.getAxisClipHeight(!a.config.axis_rotated)},i.getYAxisClipWidth=function(){var a=this;return a.getAxisClipWidth(a.config.axis_rotated)+(a.config.axis_y_inner?20:0)},i.getYAxisClipHeight=function(){var a=this;return a.getAxisClipHeight(a.config.axis_rotated)},i.initPie=function(){var a=this,b=a.d3,c=a.config;a.pie=b.layout.pie().value(function(a){return a.values.reduce(function(a,b){return a+b.value},0)}),c.data_order||a.pie.sort(null)},i.updateRadius=function(){var a=this,b=a.config,c=b.gauge_width||b.donut_width;a.radiusExpanded=Math.min(a.arcWidth,a.arcHeight)/2,a.radius=.95*a.radiusExpanded,a.innerRadiusRatio=c?(a.radius-c)/a.radius:.6,a.innerRadius=a.hasType("donut")||a.hasType("gauge")?a.radius*a.innerRadiusRatio:0},i.updateArc=function(){var a=this;a.svgArc=a.getSvgArc(),a.svgArcExpanded=a.getSvgArcExpanded(),a.svgArcExpandedSub=a.getSvgArcExpanded(.98)},i.updateAngle=function(a){var b,c,d,e,f=this,g=f.config,h=!1,i=0;return g?(f.pie(f.filterTargetsToShow(f.data.targets)).forEach(function(b){h||b.data.id!==a.data.id||(h=!0,a=b,a.index=i),i++}),isNaN(a.startAngle)&&(a.startAngle=0),isNaN(a.endAngle)&&(a.endAngle=a.startAngle),f.isGaugeType(a.data)&&(b=g.gauge_min,c=g.gauge_max,d=Math.PI/(c-b),e=a.value.375?1.175-36/g.radius:.8)*g.radius/e:0,i="translate("+c*f+","+d*f+")"),i},i.getArcRatio=function(a){var b=this,c=b.hasType("gauge")?Math.PI:2*Math.PI;return a?(a.endAngle-a.startAngle)/c:null},i.convertToArcData=function(a){return this.addName({id:a.data.id,value:a.value,ratio:this.getArcRatio(a),index:a.index})},i.textForArcLabel=function(a){var b,c,d,e,f,g=this;return g.shouldShowArcLabel()?(b=g.updateAngle(a),c=b?b.value:null,d=g.getArcRatio(b),e=a.data.id,g.hasType("gauge")||g.meetsArcLabelThreshold(d)?(f=g.getArcLabelFormat(),f?f(c,d,e):g.defaultArcValueFormat(c,d)):""):""},i.expandArc=function(b){var c,d=this;return d.transiting?void(c=a.setInterval(function(){d.transiting||(a.clearInterval(c),d.legend.selectAll(".c3-legend-item-focused").size()>0&&d.expandArc(b))},10)):(b=d.mapToTargetIds(b),void d.svg.selectAll(d.selectorTargets(b,"."+l.chartArc)).each(function(a){d.shouldExpand(a.data.id)&&d.d3.select(this).selectAll("path").transition().duration(d.expandDuration(a.data.id)).attr("d",d.svgArcExpanded).transition().duration(2*d.expandDuration(a.data.id)).attr("d",d.svgArcExpandedSub).each(function(a){d.isDonutType(a.data)})}))},i.unexpandArc=function(a){var b=this;b.transiting||(a=b.mapToTargetIds(a),b.svg.selectAll(b.selectorTargets(a,"."+l.chartArc)).selectAll("path").transition().duration(function(a){return b.expandDuration(a.data.id)}).attr("d",b.svgArc),b.svg.selectAll("."+l.arc).style("opacity",1))},i.expandDuration=function(a){var b=this,c=b.config;return b.isDonutType(a)?c.donut_expand_duration:b.isGaugeType(a)?c.gauge_expand_duration:b.isPieType(a)?c.pie_expand_duration:50},i.shouldExpand=function(a){var b=this,c=b.config;return b.isDonutType(a)&&c.donut_expand||b.isGaugeType(a)&&c.gauge_expand||b.isPieType(a)&&c.pie_expand},i.shouldShowArcLabel=function(){var a=this,b=a.config,c=!0;return a.hasType("donut")?c=b.donut_label_show:a.hasType("pie")&&(c=b.pie_label_show),c},i.meetsArcLabelThreshold=function(a){var b=this,c=b.config,d=b.hasType("donut")?c.donut_label_threshold:c.pie_label_threshold;return a>=d},i.getArcLabelFormat=function(){var a=this,b=a.config,c=b.pie_label_format;return a.hasType("gauge")?c=b.gauge_label_format:a.hasType("donut")&&(c=b.donut_label_format),c},i.getArcTitle=function(){var a=this;return a.hasType("donut")?a.config.donut_title:""},i.updateTargetsForArc=function(a){var b,c,d=this,e=d.main,f=d.classChartArc.bind(d),g=d.classArcs.bind(d),h=d.classFocus.bind(d);b=e.select("."+l.chartArcs).selectAll("."+l.chartArc).data(d.pie(a)).attr("class",function(a){return f(a)+h(a.data)}),c=b.enter().append("g").attr("class",f),c.append("g").attr("class",g),c.append("text").attr("dy",d.hasType("gauge")?"-.1em":".35em").style("opacity",0).style("text-anchor","middle").style("pointer-events","none")},i.initArc=function(){var a=this;a.arcs=a.main.select("."+l.chart).append("g").attr("class",l.chartArcs).attr("transform",a.getTranslate("arc")),a.arcs.append("text").attr("class",l.chartArcsTitle).style("text-anchor","middle").text(a.getArcTitle())},i.redrawArc=function(a,b,c){var d,e=this,f=e.d3,g=e.config,h=e.main;d=h.selectAll("."+l.arcs).selectAll("."+l.arc).data(e.arcData.bind(e)),d.enter().append("path").attr("class",e.classArc.bind(e)).style("fill",function(a){return e.color(a.data)}).style("cursor",function(a){return g.interaction_enabled&&g.data_selection_isselectable(a)?"pointer":null}).style("opacity",0).each(function(a){e.isGaugeType(a.data)&&(a.startAngle=a.endAngle=-1*(Math.PI/2)),this._current=a}),d.attr("transform",function(a){return!e.isGaugeType(a.data)&&c?"scale(0)":""}).style("opacity",function(a){return a===this._current?0:1}).on("mouseover",g.interaction_enabled?function(a){var b,c;e.transiting||(b=e.updateAngle(a),b&&(c=e.convertToArcData(b),e.expandArc(b.data.id),e.api.focus(b.data.id),e.toggleFocusLegend(b.data.id,!0),e.config.data_onmouseover(c,this)))}:null).on("mousemove",g.interaction_enabled?function(a){var b,c,d=e.updateAngle(a);d&&(b=e.convertToArcData(d),c=[b],e.showTooltip(c,this))}:null).on("mouseout",g.interaction_enabled?function(a){var b,c;e.transiting||(b=e.updateAngle(a),b&&(c=e.convertToArcData(b),e.unexpandArc(b.data.id),e.api.revert(),e.revertLegend(),e.hideTooltip(),e.config.data_onmouseout(c,this)))}:null).on("click",g.interaction_enabled?function(a,b){var c,d=e.updateAngle(a);d&&(c=e.convertToArcData(d),e.toggleShape&&e.toggleShape(this,c,b),e.config.data_onclick.call(e.api,c,this))}:null).each(function(){e.transiting=!0}).transition().duration(a).attrTween("d",function(a){var b,c=e.updateAngle(a);return c?(isNaN(this._current.startAngle)&&(this._current.startAngle=0),isNaN(this._current.endAngle)&&(this._current.endAngle=this._current.startAngle),b=f.interpolate(this._current,c),this._current=b(0),function(c){var d=b(c);return d.data=a.data,e.getArc(d,!0)}):function(){return"M 0 0"}}).attr("transform",c?"scale(1)":"").style("fill",function(a){return e.levelColor?e.levelColor(a.data.values[0].value):e.color(a.data.id)}).style("opacity",1).call(e.endall,function(){e.transiting=!1}),d.exit().transition().duration(b).style("opacity",0).remove(),h.selectAll("."+l.chartArc).select("text").style("opacity",0).attr("class",function(a){return e.isGaugeType(a.data)?l.gaugeValue:""}).text(e.textForArcLabel.bind(e)).attr("transform",e.transformForArcLabel.bind(e)).style("font-size",function(a){return e.isGaugeType(a.data)?Math.round(e.radius/5)+"px":""}).transition().duration(a).style("opacity",function(a){return e.isTargetToShow(a.data.id)&&e.isArcType(a.data)?1:0}),h.select("."+l.chartArcsTitle).style("opacity",e.hasType("donut")||e.hasType("gauge")?1:0),e.hasType("gauge")&&(e.arcs.select("."+l.chartArcsBackground).attr("d",function(){var a={data:[{value:g.gauge_max}],startAngle:-1*(Math.PI/2),endAngle:Math.PI/2};return e.getArc(a,!0,!0)}),e.arcs.select("."+l.chartArcsGaugeUnit).attr("dy",".75em").text(g.gauge_label_show?g.gauge_units:""),e.arcs.select("."+l.chartArcsGaugeMin).attr("dx",-1*(e.innerRadius+(e.radius-e.innerRadius)/2)+"px").attr("dy","1.2em").text(g.gauge_label_show?g.gauge_min:""),e.arcs.select("."+l.chartArcsGaugeMax).attr("dx",e.innerRadius+(e.radius-e.innerRadius)/2+"px").attr("dy","1.2em").text(g.gauge_label_show?g.gauge_max:""))},i.initGauge=function(){var a=this.arcs;this.hasType("gauge")&&(a.append("path").attr("class",l.chartArcsBackground),a.append("text").attr("class",l.chartArcsGaugeUnit).style("text-anchor","middle").style("pointer-events","none"),a.append("text").attr("class",l.chartArcsGaugeMin).style("text-anchor","middle").style("pointer-events","none"),a.append("text").attr("class",l.chartArcsGaugeMax).style("text-anchor","middle").style("pointer-events","none"))},i.getGaugeLabelHeight=function(){return this.config.gauge_label_show?20:0},i.initRegion=function(){var a=this;a.region=a.main.append("g").attr("clip-path",a.clipPath).attr("class",l.regions)},i.updateRegion=function(a){var b=this,c=b.config;b.region.style("visibility",b.hasArcType()?"hidden":"visible"),b.mainRegion=b.main.select("."+l.regions).selectAll("."+l.region).data(c.regions),b.mainRegion.enter().append("g").attr("class",b.classRegion.bind(b)).append("rect").style("fill-opacity",0),b.mainRegion.exit().transition().duration(a).style("opacity",0).remove()},i.redrawRegion=function(a){var b=this,c=b.mainRegion.selectAll("rect"),d=b.regionX.bind(b),e=b.regionY.bind(b),f=b.regionWidth.bind(b),g=b.regionHeight.bind(b);return[(a?c.transition():c).attr("x",d).attr("y",e).attr("width",f).attr("height",g).style("fill-opacity",function(a){return m(a.opacity)?a.opacity:.1})]},i.regionX=function(a){var b,c=this,d=c.config,e="y"===a.axis?c.y:c.y2;return b="y"===a.axis||"y2"===a.axis?d.axis_rotated&&"start"in a?e(a.start):0:d.axis_rotated?0:"start"in a?c.x(c.isTimeSeries()?c.parseDate(a.start):a.start):0},i.regionY=function(a){var b,c=this,d=c.config,e="y"===a.axis?c.y:c.y2;return b="y"===a.axis||"y2"===a.axis?d.axis_rotated?0:"end"in a?e(a.end):0:d.axis_rotated&&"start"in a?c.x(c.isTimeSeries()?c.parseDate(a.start):a.start):0},i.regionWidth=function(a){var b,c=this,d=c.config,e=c.regionX(a),f="y"===a.axis?c.y:c.y2;return b="y"===a.axis||"y2"===a.axis?d.axis_rotated&&"end"in a?f(a.end):c.width:d.axis_rotated?c.width:"end"in a?c.x(c.isTimeSeries()?c.parseDate(a.end):a.end):c.width,e>b?0:b-e},i.regionHeight=function(a){var b,c=this,d=c.config,e=this.regionY(a),f="y"===a.axis?c.y:c.y2;return b="y"===a.axis||"y2"===a.axis?d.axis_rotated?c.height:"start"in a?f(a.start):c.height:d.axis_rotated&&"end"in a?c.x(c.isTimeSeries()?c.parseDate(a.end):a.end):c.height,e>b?0:b-e},i.isRegionOnX=function(a){return!a.axis||"x"===a.axis},i.drag=function(a){var b,c,d,e,f,g,h,i,j=this,k=j.config,m=j.main,n=j.d3;j.hasArcType()||k.data_selection_enabled&&(!k.zoom_enabled||j.zoom.altDomain)&&k.data_selection_multiple&&(b=j.dragStart[0],c=j.dragStart[1],d=a[0],e=a[1],f=Math.min(b,d),g=Math.max(b,d),h=k.data_selection_grouped?j.margin.top:Math.min(c,e),i=k.data_selection_grouped?j.height:Math.max(c,e),m.select("."+l.dragarea).attr("x",f).attr("y",h).attr("width",g-f).attr("height",i-h),m.selectAll("."+l.shapes).selectAll("."+l.shape).filter(function(a){return k.data_selection_isselectable(a)}).each(function(a,b){var c,d,e,k,m,o,p=n.select(this),q=p.classed(l.SELECTED),r=p.classed(l.INCLUDED),s=!1;if(p.classed(l.circle))c=1*p.attr("cx"),d=1*p.attr("cy"),m=j.togglePoint,s=c>f&&g>c&&d>h&&i>d;else{if(!p.classed(l.bar))return;o=y(this),c=o.x,d=o.y,e=o.width,k=o.height,m=j.togglePath,s=!(c>g||f>c+e||d>i||h>d+k)}s^r&&(p.classed(l.INCLUDED,!r),p.classed(l.SELECTED,!q),m.call(j,!q,p,a,b))}))},i.dragstart=function(a){var b=this,c=b.config;b.hasArcType()||c.data_selection_enabled&&(b.dragStart=a,b.main.select("."+l.chart).append("rect").attr("class",l.dragarea).style("opacity",.1),b.dragging=!0)},i.dragend=function(){var a=this,b=a.config;a.hasArcType()||b.data_selection_enabled&&(a.main.select("."+l.dragarea).transition().duration(100).style("opacity",0).remove(),a.main.selectAll("."+l.shape).classed(l.INCLUDED,!1),a.dragging=!1)},i.selectPoint=function(a,b,c){var d=this,e=d.config,f=(e.axis_rotated?d.circleY:d.circleX).bind(d),g=(e.axis_rotated?d.circleX:d.circleY).bind(d),h=d.pointSelectR.bind(d);e.data_onselected.call(d.api,b,a.node()),d.main.select("."+l.selectedCircles+d.getTargetSelectorSuffix(b.id)).selectAll("."+l.selectedCircle+"-"+c).data([b]).enter().append("circle").attr("class",function(){return d.generateClass(l.selectedCircle,c)}).attr("cx",f).attr("cy",g).attr("stroke",function(){return d.color(b)}).attr("r",function(a){return 1.4*d.pointSelectR(a)}).transition().duration(100).attr("r",h)},i.unselectPoint=function(a,b,c){var d=this;d.config.data_onunselected.call(d.api,b,a.node()),d.main.select("."+l.selectedCircles+d.getTargetSelectorSuffix(b.id)).selectAll("."+l.selectedCircle+"-"+c).transition().duration(100).attr("r",0).remove()},i.togglePoint=function(a,b,c,d){a?this.selectPoint(b,c,d):this.unselectPoint(b,c,d)},i.selectPath=function(a,b){var c=this;c.config.data_onselected.call(c,b,a.node()),a.transition().duration(100).style("fill",function(){return c.d3.rgb(c.color(b)).brighter(.75)})},i.unselectPath=function(a,b){var c=this;c.config.data_onunselected.call(c,b,a.node()),a.transition().duration(100).style("fill",function(){return c.color(b)})},i.togglePath=function(a,b,c,d){a?this.selectPath(b,c,d):this.unselectPath(b,c,d)},i.getToggle=function(a,b){var c,d=this;return"circle"===a.nodeName?c=d.isStepType(b)?function(){}:d.togglePoint:"path"===a.nodeName&&(c=d.togglePath),c},i.toggleShape=function(a,b,c){var d=this,e=d.d3,f=d.config,g=e.select(a),h=g.classed(l.SELECTED),i=d.getToggle(a,b).bind(d);f.data_selection_enabled&&f.data_selection_isselectable(b)&&(f.data_selection_multiple||d.main.selectAll("."+l.shapes+(f.data_selection_grouped?d.getTargetSelectorSuffix(b.id):"")).selectAll("."+l.shape).each(function(a,b){var c=e.select(this);c.classed(l.SELECTED)&&i(!1,c.classed(l.SELECTED,!1),a,b)}),g.classed(l.SELECTED,!h),i(!h,g,b,c))},i.initBrush=function(){var a=this,b=a.d3;a.brush=b.svg.brush().on("brush",function(){a.redrawForBrush()}),a.brush.update=function(){return a.context&&a.context.select("."+l.brush).call(this),this},a.brush.scale=function(b){return a.config.axis_rotated?this.y(b):this.x(b)}},i.initSubchart=function(){var a=this,b=a.config,c=a.context=a.svg.append("g").attr("transform",a.getTranslate("context")),d=b.subchart_show?"visible":"hidden";c.style("visibility",d),c.append("g").attr("clip-path",a.clipPathForSubchart).attr("class",l.chart),c.select("."+l.chart).append("g").attr("class",l.chartBars),c.select("."+l.chart).append("g").attr("class",l.chartLines),c.append("g").attr("clip-path",a.clipPath).attr("class",l.brush).call(a.brush),a.axes.subx=c.append("g").attr("class",l.axisX).attr("transform",a.getTranslate("subx")).attr("clip-path",b.axis_rotated?"":a.clipPathForXAxis).style("visibility",b.subchart_axis_x_show?d:"hidden")},i.updateTargetsForSubchart=function(a){var b,c,d,e,f=this,g=f.context,h=f.config,i=f.classChartBar.bind(f),j=f.classBars.bind(f),k=f.classChartLine.bind(f),m=f.classLines.bind(f),n=f.classAreas.bind(f);h.subchart_show&&(e=g.select("."+l.chartBars).selectAll("."+l.chartBar).data(a).attr("class",i),d=e.enter().append("g").style("opacity",0).attr("class",i),d.append("g").attr("class",j),c=g.select("."+l.chartLines).selectAll("."+l.chartLine).data(a).attr("class",k),b=c.enter().append("g").style("opacity",0).attr("class",k),b.append("g").attr("class",m),b.append("g").attr("class",n),g.selectAll("."+l.brush+" rect").attr(h.axis_rotated?"width":"height",h.axis_rotated?f.width2:f.height2))},i.updateBarForSubchart=function(a){var b=this;b.contextBar=b.context.selectAll("."+l.bars).selectAll("."+l.bar).data(b.barData.bind(b)),b.contextBar.enter().append("path").attr("class",b.classBar.bind(b)).style("stroke","none").style("fill",b.color),b.contextBar.style("opacity",b.initialOpacity.bind(b)),b.contextBar.exit().transition().duration(a).style("opacity",0).remove()},i.redrawBarForSubchart=function(a,b,c){(b?this.contextBar.transition(Math.random().toString()).duration(c):this.contextBar).attr("d",a).style("opacity",1)},i.updateLineForSubchart=function(a){var b=this;b.contextLine=b.context.selectAll("."+l.lines).selectAll("."+l.line).data(b.lineData.bind(b)),b.contextLine.enter().append("path").attr("class",b.classLine.bind(b)).style("stroke",b.color),b.contextLine.style("opacity",b.initialOpacity.bind(b)),b.contextLine.exit().transition().duration(a).style("opacity",0).remove()},i.redrawLineForSubchart=function(a,b,c){(b?this.contextLine.transition(Math.random().toString()).duration(c):this.contextLine).attr("d",a).style("opacity",1)},i.updateAreaForSubchart=function(a){var b=this,c=b.d3;b.contextArea=b.context.selectAll("."+l.areas).selectAll("."+l.area).data(b.lineData.bind(b)),b.contextArea.enter().append("path").attr("class",b.classArea.bind(b)).style("fill",b.color).style("opacity",function(){return b.orgAreaOpacity=+c.select(this).style("opacity"),0}),b.contextArea.style("opacity",0),b.contextArea.exit().transition().duration(a).style("opacity",0).remove()},i.redrawAreaForSubchart=function(a,b,c){(b?this.contextArea.transition(Math.random().toString()).duration(c):this.contextArea).attr("d",a).style("fill",this.color).style("opacity",this.orgAreaOpacity)},i.redrawSubchart=function(a,b,c,d,e,f,g){var h,i,j,k=this,l=k.d3,m=k.config;k.context.style("visibility",m.subchart_show?"visible":"hidden"),m.subchart_show&&(l.event&&"zoom"===l.event.type&&k.brush.extent(k.x.orgDomain()).update(),a&&(k.brush.empty()||k.brush.extent(k.x.orgDomain()).update(),h=k.generateDrawArea(e,!0),i=k.generateDrawBar(f,!0),j=k.generateDrawLine(g,!0),k.updateBarForSubchart(c),k.updateLineForSubchart(c),k.updateAreaForSubchart(c),k.redrawBarForSubchart(i,c,c),k.redrawLineForSubchart(j,c,c),k.redrawAreaForSubchart(h,c,c)))},i.redrawForBrush=function(){var a=this,b=a.x;a.redraw({withTransition:!1,withY:a.config.zoom_rescale,withSubchart:!1,withUpdateXDomain:!0,withDimension:!1}),a.config.subchart_onbrush.call(a.api,b.orgDomain())},i.transformContext=function(a,b){var c,d=this;b&&b.axisSubX?c=b.axisSubX:(c=d.context.select("."+l.axisX),a&&(c=c.transition())),d.context.attr("transform",d.getTranslate("context")),c.attr("transform",d.getTranslate("subx"))},i.getDefaultExtent=function(){var a=this,b=a.config,c=n(b.axis_x_extent)?b.axis_x_extent(a.getXDomain(a.data.targets)):b.axis_x_extent;return a.isTimeSeries()&&(c=[a.parseDate(c[0]),a.parseDate(c[1])]),c},i.initZoom=function(){var a,b=this,c=b.d3,d=b.config;b.zoom=c.behavior.zoom().on("zoomstart",function(){a=c.event.sourceEvent,b.zoom.altDomain=c.event.sourceEvent.altKey?b.x.orgDomain():null,d.zoom_onzoomstart.call(b.api,c.event.sourceEvent)}).on("zoom",function(){b.redrawForZoom.call(b)}).on("zoomend",function(){var e=c.event.sourceEvent;e&&a.clientX===e.clientX&&a.clientY===e.clientY||(b.redrawEventRect(),b.updateZoom(),d.zoom_onzoomend.call(b.api,b.x.orgDomain()))}),b.zoom.scale=function(a){return d.axis_rotated?this.y(a):this.x(a)},b.zoom.orgScaleExtent=function(){var a=d.zoom_extent?d.zoom_extent:[1,10];return[a[0],Math.max(b.getMaxDataCount()/a[1],a[1])]},b.zoom.updateScaleExtent=function(){var a=t(b.x.orgDomain())/t(b.getZoomDomain()),c=this.orgScaleExtent();return this.scaleExtent([c[0]*a,c[1]*a]),this}},i.getZoomDomain=function(){var a=this,b=a.config,c=a.d3,d=c.min([a.orgXDomain[0],b.zoom_x_min]),e=c.max([a.orgXDomain[1],b.zoom_x_max]);return[d,e]},i.updateZoom=function(){var a=this,b=a.config.zoom_enabled?a.zoom:function(){};a.main.select("."+l.zoomRect).call(b).on("dblclick.zoom",null),a.main.selectAll("."+l.eventRect).call(b).on("dblclick.zoom",null)},i.redrawForZoom=function(){var a=this,b=a.d3,c=a.config,d=a.zoom,e=a.x;if(c.zoom_enabled&&0!==a.filterTargetsToShow(a.data.targets).length){if("mousemove"===b.event.sourceEvent.type&&d.altDomain)return e.domain(d.altDomain),void d.scale(e).updateScaleExtent();a.isCategorized()&&e.orgDomain()[0]===a.orgXDomain[0]&&e.domain([a.orgXDomain[0]-1e-10,e.orgDomain()[1]]),a.redraw({withTransition:!1,withY:c.zoom_rescale,withSubchart:!1,withEventRect:!1,withDimension:!1}),"mousemove"===b.event.sourceEvent.type&&(a.cancelClick=!0),c.zoom_onzoom.call(a.api,e.orgDomain())}},i.generateColor=function(){var a=this,b=a.config,c=a.d3,d=b.data_colors,e=v(b.color_pattern)?b.color_pattern:c.scale.category10().range(),f=b.data_color,g=[];return function(a){var b,c=a.id||a.data&&a.data.id||a;return d[c]instanceof Function?b=d[c](a):d[c]?b=d[c]:(g.indexOf(c)<0&&g.push(c),b=e[g.indexOf(c)%e.length],d[c]=b),f instanceof Function?f(b,a):b}},i.generateLevelColor=function(){var a=this,b=a.config,c=b.color_pattern,d=b.color_threshold,e="value"===d.unit,f=d.values&&d.values.length?d.values:[],g=d.max||100;return v(b.color_threshold)?function(a){var b,d,h=c[c.length-1];for(b=0;b=0?l.focused:"")},i.classDefocused=function(a){return" "+(this.defocusedTargetIds.indexOf(a.id)>=0?l.defocused:"")},i.classChartText=function(a){return l.chartText+this.classTarget(a.id)},i.classChartLine=function(a){return l.chartLine+this.classTarget(a.id)},i.classChartBar=function(a){return l.chartBar+this.classTarget(a.id)},i.classChartArc=function(a){return l.chartArc+this.classTarget(a.data.id)},i.getTargetSelectorSuffix=function(a){return a||0===a?("-"+a).replace(/[\s?!@#$%^&*()_=+,.<>'":;\[\]\/|~`{}\\]/g,"-"):""},i.selectorTarget=function(a,b){return(b||"")+"."+l.target+this.getTargetSelectorSuffix(a)},i.selectorTargets=function(a,b){var c=this;return a=a||[],a.length?a.map(function(a){return c.selectorTarget(a,b)}):null},i.selectorLegend=function(a){return"."+l.legendItem+this.getTargetSelectorSuffix(a)},i.selectorLegends=function(a){var b=this;return a&&a.length?a.map(function(a){return b.selectorLegend(a)}):null};var m=i.isValue=function(a){return a||0===a},n=i.isFunction=function(a){return"function"==typeof a},o=i.isString=function(a){return"string"==typeof a},p=i.isUndefined=function(a){return"undefined"==typeof a},q=i.isDefined=function(a){return"undefined"!=typeof a},r=i.ceil10=function(a){return 10*Math.ceil(a/10)},s=i.asHalfPixel=function(a){return Math.ceil(a)+.5},t=i.diffDomain=function(a){return a[1]-a[0]},u=i.isEmpty=function(a){return"undefined"==typeof a||null===a||o(a)&&0===a.length||"object"==typeof a&&0===Object.keys(a).length},v=i.notEmpty=function(a){return!i.isEmpty(a)},w=i.getOption=function(a,b,c){return q(a[b])?a[b]:c},x=i.hasValue=function(a,b){var c=!1;return Object.keys(a).forEach(function(d){a[d]===b&&(c=!0)}),c},y=i.getPathBox=function(a){var b=a.getBoundingClientRect(),c=[a.pathSegList.getItem(0),a.pathSegList.getItem(1)],d=c[0].x,e=Math.min(c[0].y,c[1].y);return{x:d,y:e,width:b.width,height:b.height}};h.focus=function(a){var b,c=this.internal;a=c.mapToTargetIds(a),b=c.svg.selectAll(c.selectorTargets(a.filter(c.isTargetToShow,c))),this.revert(),this.defocus(),b.classed(l.focused,!0).classed(l.defocused,!1),c.hasArcType()&&c.expandArc(a),c.toggleFocusLegend(a,!0),c.focusedTargetIds=a,c.defocusedTargetIds=c.defocusedTargetIds.filter(function(b){return a.indexOf(b)<0})},h.defocus=function(a){var b,c=this.internal;a=c.mapToTargetIds(a),b=c.svg.selectAll(c.selectorTargets(a.filter(c.isTargetToShow,c))),b.classed(l.focused,!1).classed(l.defocused,!0),c.hasArcType()&&c.unexpandArc(a),c.toggleFocusLegend(a,!1),c.focusedTargetIds=c.focusedTargetIds.filter(function(b){return a.indexOf(b)<0}),c.defocusedTargetIds=a},h.revert=function(a){var b,c=this.internal;a=c.mapToTargetIds(a),b=c.svg.selectAll(c.selectorTargets(a)),b.classed(l.focused,!1).classed(l.defocused,!1),c.hasArcType()&&c.unexpandArc(a),c.config.legend_show&&(c.showLegend(a.filter(c.isLegendToShow.bind(c))),c.legend.selectAll(c.selectorLegends(a)).filter(function(){return c.d3.select(this).classed(l.legendItemFocused)}).classed(l.legendItemFocused,!1)),c.focusedTargetIds=[],c.defocusedTargetIds=[]},h.show=function(a,b){var c,d=this.internal;a=d.mapToTargetIds(a), -b=b||{},d.removeHiddenTargetIds(a),c=d.svg.selectAll(d.selectorTargets(a)),c.transition().style("opacity",1,"important").call(d.endall,function(){c.style("opacity",null).style("opacity",1)}),b.withLegend&&d.showLegend(a),d.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0,withLegend:!0})},h.hide=function(a,b){var c,d=this.internal;a=d.mapToTargetIds(a),b=b||{},d.addHiddenTargetIds(a),c=d.svg.selectAll(d.selectorTargets(a)),c.transition().style("opacity",0,"important").call(d.endall,function(){c.style("opacity",null).style("opacity",0)}),b.withLegend&&d.hideLegend(a),d.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0,withLegend:!0})},h.toggle=function(a,b){var c=this,d=this.internal;d.mapToTargetIds(a).forEach(function(a){d.isTargetToShow(a)?c.hide(a,b):c.show(a,b)})},h.zoom=function(a){var b=this.internal;return a&&(b.isTimeSeries()&&(a=a.map(function(a){return b.parseDate(a)})),b.brush.extent(a),b.redraw({withUpdateXDomain:!0,withY:b.config.zoom_rescale}),b.config.zoom_onzoom.call(this,b.x.orgDomain())),b.brush.extent()},h.zoom.enable=function(a){var b=this.internal;b.config.zoom_enabled=a,b.updateAndRedraw()},h.unzoom=function(){var a=this.internal;a.brush.clear().update(),a.redraw({withUpdateXDomain:!0})},h.zoom.max=function(a){var b=this.internal,c=b.config,d=b.d3;return 0===a||a?void(c.zoom_x_max=d.max([b.orgXDomain[1],a])):c.zoom_x_max},h.zoom.min=function(a){var b=this.internal,c=b.config,d=b.d3;return 0===a||a?void(c.zoom_x_min=d.min([b.orgXDomain[0],a])):c.zoom_x_min},h.zoom.range=function(a){return arguments.length?(q(a.max)&&this.domain.max(a.max),void(q(a.min)&&this.domain.min(a.min))):{max:this.domain.max(),min:this.domain.min()}},h.load=function(a){var b=this.internal,c=b.config;return a.xs&&b.addXs(a.xs),"names"in a&&h.data.names.bind(this)(a.names),"classes"in a&&Object.keys(a.classes).forEach(function(b){c.data_classes[b]=a.classes[b]}),"categories"in a&&b.isCategorized()&&(c.axis_x_categories=a.categories),"axes"in a&&Object.keys(a.axes).forEach(function(b){c.data_axes[b]=a.axes[b]}),"colors"in a&&Object.keys(a.colors).forEach(function(b){c.data_colors[b]=a.colors[b]}),"cacheIds"in a&&b.hasCaches(a.cacheIds)?void b.load(b.getCaches(a.cacheIds),a.done):void("unload"in a?b.unload(b.mapToTargetIds("boolean"==typeof a.unload&&a.unload?null:a.unload),function(){b.loadFromArgs(a)}):b.loadFromArgs(a))},h.unload=function(a){var b=this.internal;a=a||{},a instanceof Array?a={ids:a}:"string"==typeof a&&(a={ids:[a]}),b.unload(b.mapToTargetIds(a.ids),function(){b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0,withLegend:!0}),a.done&&a.done()})},h.flow=function(a){var b,c,d,e,f,g,h,i,j=this.internal,k=[],l=j.getMaxDataCount(),n=0,o=0;if(a.json)c=j.convertJsonToData(a.json,a.keys);else if(a.rows)c=j.convertRowsToData(a.rows);else{if(!a.columns)return;c=j.convertColumnsToData(a.columns)}b=j.convertDataToTargets(c,!0),j.data.targets.forEach(function(a){var c,d,e=!1;for(c=0;cd;d++)b[c].values[d].index=o+d,j.isTimeSeries()||(b[c].values[d].x=o+d);a.values=a.values.concat(b[c].values),b.splice(c,1);break}e||k.push(a.id)}),j.data.targets.forEach(function(a){var b,c;for(b=0;bc;c++)a.values.push({id:a.id,index:o+c,x:j.isTimeSeries()?j.getOtherTargetX(o+c):o+c,value:null})}),j.data.targets.length&&b.forEach(function(a){var b,c=[];for(b=j.data.targets[0].values[0].index;o>b;b++)c.push({id:a.id,index:b,x:j.isTimeSeries()?j.getOtherTargetX(b):b,value:null});a.values.forEach(function(a){a.index+=o,j.isTimeSeries()||(a.x+=o)}),a.values=c.concat(a.values)}),j.data.targets=j.data.targets.concat(b),d=j.getMaxDataCount(),f=j.data.targets[0],g=f.values[0],q(a.to)?(n=0,i=j.isTimeSeries()?j.parseDate(a.to):a.to,f.values.forEach(function(a){a.x1?f.values[f.values.length-1].x-g.x:g.x-j.getXDomain(j.data.targets)[0]:1,e=[g.x-h,g.x],j.updateXDomain(null,!0,!0,!1,e)),j.updateTargets(j.data.targets),j.redraw({flow:{index:g.index,length:n,duration:m(a.duration)?a.duration:j.config.transition_duration,done:a.done,orgDataCount:l},withLegend:!0,withTransition:l>1,withTrimXDomain:!1,withUpdateXAxis:!0})},i.generateFlow=function(a){var b=this,c=b.config,d=b.d3;return function(){var e,f,g,h=a.targets,i=a.flow,j=a.drawBar,k=a.drawLine,m=a.drawArea,n=a.cx,o=a.cy,p=a.xv,q=a.xForText,r=a.yForText,s=a.duration,u=1,v=i.index,w=i.length,x=b.getValueOnIndex(b.data.targets[0].values,v),y=b.getValueOnIndex(b.data.targets[0].values,v+w),z=b.x.domain(),A=i.duration||s,B=i.done||function(){},C=b.generateWait(),D=b.xgrid||d.selectAll([]),E=b.xgridLines||d.selectAll([]),F=b.mainRegion||d.selectAll([]),G=b.mainText||d.selectAll([]),H=b.mainBar||d.selectAll([]),I=b.mainLine||d.selectAll([]),J=b.mainArea||d.selectAll([]),K=b.mainCircle||d.selectAll([]);b.flowing=!0,b.data.targets.forEach(function(a){a.values.splice(0,w)}),g=b.updateXDomain(h,!0,!0),b.updateXGrid&&b.updateXGrid(!0),i.orgDataCount?e=1===i.orgDataCount||x.x===y.x?b.x(z[0])-b.x(g[0]):b.isTimeSeries()?b.x(z[0])-b.x(g[0]):b.x(x.x)-b.x(y.x):1!==b.data.targets[0].values.length?e=b.x(z[0])-b.x(g[0]):b.isTimeSeries()?(x=b.getValueOnIndex(b.data.targets[0].values,0),y=b.getValueOnIndex(b.data.targets[0].values,b.data.targets[0].values.length-1),e=b.x(x.x)-b.x(y.x)):e=t(g)/2,u=t(z)/t(g),f="translate("+e+",0) scale("+u+",1)",b.hideXGridFocus(),d.transition().ease("linear").duration(A).each(function(){C.add(b.axes.x.transition().call(b.xAxis)),C.add(H.transition().attr("transform",f)),C.add(I.transition().attr("transform",f)),C.add(J.transition().attr("transform",f)),C.add(K.transition().attr("transform",f)),C.add(G.transition().attr("transform",f)),C.add(F.filter(b.isRegionOnX).transition().attr("transform",f)),C.add(D.transition().attr("transform",f)),C.add(E.transition().attr("transform",f))}).call(C,function(){var a,d=[],e=[],f=[];if(w){for(a=0;w>a;a++)d.push("."+l.shape+"-"+(v+a)),e.push("."+l.text+"-"+(v+a)),f.push("."+l.eventRect+"-"+(v+a));b.svg.selectAll("."+l.shapes).selectAll(d).remove(),b.svg.selectAll("."+l.texts).selectAll(e).remove(),b.svg.selectAll("."+l.eventRects).selectAll(f).remove(),b.svg.select("."+l.xgrid).remove()}D.attr("transform",null).attr(b.xgridAttr),E.attr("transform",null),E.select("line").attr("x1",c.axis_rotated?0:p).attr("x2",c.axis_rotated?b.width:p),E.select("text").attr("x",c.axis_rotated?b.width:0).attr("y",p),H.attr("transform",null).attr("d",j),I.attr("transform",null).attr("d",k),J.attr("transform",null).attr("d",m),K.attr("transform",null).attr("cx",n).attr("cy",o),G.attr("transform",null).attr("x",q).attr("y",r).style("fill-opacity",b.opacityForText.bind(b)),F.attr("transform",null),F.select("rect").filter(b.isRegionOnX).attr("x",b.regionX.bind(b)).attr("width",b.regionWidth.bind(b)),c.interaction_enabled&&b.redrawEventRect(),B(),b.flowing=!1})}},h.selected=function(a){var b=this.internal,c=b.d3;return c.merge(b.main.selectAll("."+l.shapes+b.getTargetSelectorSuffix(a)).selectAll("."+l.shape).filter(function(){return c.select(this).classed(l.SELECTED)}).map(function(a){return a.map(function(a){var b=a.__data__;return b.data?b.data:b})}))},h.select=function(a,b,c){var d=this.internal,e=d.d3,f=d.config;f.data_selection_enabled&&d.main.selectAll("."+l.shapes).selectAll("."+l.shape).each(function(g,h){var i=e.select(this),j=g.data?g.data.id:g.id,k=d.getToggle(this,g).bind(d),m=f.data_selection_grouped||!a||a.indexOf(j)>=0,n=!b||b.indexOf(h)>=0,o=i.classed(l.SELECTED);i.classed(l.line)||i.classed(l.area)||(m&&n?f.data_selection_isselectable(g)&&!o&&k(!0,i.classed(l.SELECTED,!0),g,h):q(c)&&c&&o&&k(!1,i.classed(l.SELECTED,!1),g,h))})},h.unselect=function(a,b){var c=this.internal,d=c.d3,e=c.config;e.data_selection_enabled&&c.main.selectAll("."+l.shapes).selectAll("."+l.shape).each(function(f,g){var h=d.select(this),i=f.data?f.data.id:f.id,j=c.getToggle(this,f).bind(c),k=e.data_selection_grouped||!a||a.indexOf(i)>=0,m=!b||b.indexOf(g)>=0,n=h.classed(l.SELECTED);h.classed(l.line)||h.classed(l.area)||k&&m&&e.data_selection_isselectable(f)&&n&&j(!1,h.classed(l.SELECTED,!1),f,g)})},h.transform=function(a,b){var c=this.internal,d=["pie","donut"].indexOf(a)>=0?{withTransform:!0}:null;c.transformTo(b,a,d)},i.transformTo=function(a,b,c){var d=this,e=!d.hasArcType(),f=c||{withTransitionForAxis:e};f.withTransitionForTransform=!1,d.transiting=!1,d.setTargetType(a,b),d.updateTargets(d.data.targets),d.updateAndRedraw(f)},h.groups=function(a){var b=this.internal,c=b.config;return p(a)?c.data_groups:(c.data_groups=a,b.redraw(),c.data_groups)},h.xgrids=function(a){var b=this.internal,c=b.config;return a?(c.grid_x_lines=a,b.redrawWithoutRescale(),c.grid_x_lines):c.grid_x_lines},h.xgrids.add=function(a){var b=this.internal;return this.xgrids(b.config.grid_x_lines.concat(a?a:[]))},h.xgrids.remove=function(a){var b=this.internal;b.removeGridLines(a,!0)},h.ygrids=function(a){var b=this.internal,c=b.config;return a?(c.grid_y_lines=a,b.redrawWithoutRescale(),c.grid_y_lines):c.grid_y_lines},h.ygrids.add=function(a){var b=this.internal;return this.ygrids(b.config.grid_y_lines.concat(a?a:[]))},h.ygrids.remove=function(a){var b=this.internal;b.removeGridLines(a,!1)},h.regions=function(a){var b=this.internal,c=b.config;return a?(c.regions=a,b.redrawWithoutRescale(),c.regions):c.regions},h.regions.add=function(a){var b=this.internal,c=b.config;return a?(c.regions=c.regions.concat(a),b.redrawWithoutRescale(),c.regions):c.regions},h.regions.remove=function(a){var b,c,d,e=this.internal,f=e.config;return a=a||{},b=e.getOption(a,"duration",f.transition_duration),c=e.getOption(a,"classes",[l.region]),d=e.main.select("."+l.regions).selectAll(c.map(function(a){return"."+a})),(b?d.transition().duration(b):d).style("opacity",0).remove(),f.regions=f.regions.filter(function(a){var b=!1;return a["class"]?(a["class"].split(" ").forEach(function(a){c.indexOf(a)>=0&&(b=!0)}),!b):!0}),f.regions},h.data=function(a){var b=this.internal.data.targets;return"undefined"==typeof a?b:b.filter(function(b){return[].concat(a).indexOf(b.id)>=0})},h.data.shown=function(a){return this.internal.filterTargetsToShow(this.data(a))},h.data.values=function(a){var b,c=null;return a&&(b=this.data(a),c=b[0]?b[0].values.map(function(a){return a.value}):null),c},h.data.names=function(a){return this.internal.clearLegendItemTextBoxCache(),this.internal.updateDataAttributes("names",a)},h.data.colors=function(a){return this.internal.updateDataAttributes("colors",a)},h.data.axes=function(a){return this.internal.updateDataAttributes("axes",a)},h.category=function(a,b){var c=this.internal,d=c.config;return arguments.length>1&&(d.axis_x_categories[a]=b,c.redraw()),d.axis_x_categories[a]},h.categories=function(a){var b=this.internal,c=b.config;return arguments.length?(c.axis_x_categories=a,b.redraw(),c.axis_x_categories):c.axis_x_categories},h.color=function(a){var b=this.internal;return b.color(a)},h.x=function(a){var b=this.internal;return arguments.length&&(b.updateTargetX(b.data.targets,a),b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0})),b.data.xs},h.xs=function(a){var b=this.internal;return arguments.length&&(b.updateTargetXs(b.data.targets,a),b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0})),b.data.xs},h.axis=function(){},h.axis.labels=function(a){var b=this.internal;arguments.length&&(Object.keys(a).forEach(function(c){b.axis.setLabelText(c,a[c])}),b.axis.updateLabels())},h.axis.max=function(a){var b=this.internal,c=b.config;return arguments.length?("object"==typeof a?(m(a.x)&&(c.axis_x_max=a.x),m(a.y)&&(c.axis_y_max=a.y),m(a.y2)&&(c.axis_y2_max=a.y2)):c.axis_y_max=c.axis_y2_max=a,void b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0})):{x:c.axis_x_max,y:c.axis_y_max,y2:c.axis_y2_max}},h.axis.min=function(a){var b=this.internal,c=b.config;return arguments.length?("object"==typeof a?(m(a.x)&&(c.axis_x_min=a.x),m(a.y)&&(c.axis_y_min=a.y),m(a.y2)&&(c.axis_y2_min=a.y2)):c.axis_y_min=c.axis_y2_min=a,void b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0})):{x:c.axis_x_min,y:c.axis_y_min,y2:c.axis_y2_min}},h.axis.range=function(a){return arguments.length?(q(a.max)&&this.axis.max(a.max),void(q(a.min)&&this.axis.min(a.min))):{max:this.axis.max(),min:this.axis.min()}},h.legend=function(){},h.legend.show=function(a){var b=this.internal;b.showLegend(b.mapToTargetIds(a)),b.updateAndRedraw({withLegend:!0})},h.legend.hide=function(a){var b=this.internal;b.hideLegend(b.mapToTargetIds(a)),b.updateAndRedraw({withLegend:!0})},h.resize=function(a){var b=this.internal,c=b.config;c.size_width=a?a.width:null,c.size_height=a?a.height:null,this.flush()},h.flush=function(){var a=this.internal;a.updateAndRedraw({withLegend:!0,withTransition:!1,withTransitionForTransform:!1})},h.destroy=function(){var b=this.internal;if(a.clearInterval(b.intervalForObserveInserted),void 0!==b.resizeTimeout&&a.clearTimeout(b.resizeTimeout),a.detachEvent)a.detachEvent("onresize",b.resizeFunction);else if(a.removeEventListener)a.removeEventListener("resize",b.resizeFunction);else{var c=a.onresize;c&&c.add&&c.remove&&c.remove(b.resizeFunction)}return b.selectChart.classed("c3",!1).html(""),Object.keys(b).forEach(function(a){b[a]=null}),null},h.tooltip=function(){},h.tooltip.show=function(a){var b,c,d=this.internal;a.mouse&&(c=a.mouse),a.data?d.isMultipleX()?(c=[d.x(a.data.x),d.getYScale(a.data.id)(a.data.value)],b=null):b=m(a.data.index)?a.data.index:d.getIndexByX(a.data.x):"undefined"!=typeof a.x?b=d.getIndexByX(a.x):"undefined"!=typeof a.index&&(b=a.index),d.dispatchEvent("mouseover",b,c),d.dispatchEvent("mousemove",b,c),d.config.tooltip_onshow.call(d,a.data)},h.tooltip.hide=function(){this.internal.dispatchEvent("mouseout",0),this.internal.config.tooltip_onhide.call(this)};var z;i.isSafari=function(){var b=a.navigator.userAgent;return b.indexOf("Safari")>=0&&b.indexOf("Chrome")<0},i.isChrome=function(){var b=a.navigator.userAgent;return b.indexOf("Chrome")>=0},Function.prototype.bind||(Function.prototype.bind=function(a){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var b=Array.prototype.slice.call(arguments,1),c=this,d=function(){},e=function(){return c.apply(this instanceof d?this:a,b.concat(Array.prototype.slice.call(arguments)))};return d.prototype=this.prototype,e.prototype=new d,e}),"function"==typeof define&&define.amd?define("c3",["d3"],function(){return k}):"undefined"!=typeof exports&&"undefined"!=typeof module?module.exports=k:a.c3=k}(window); \ No newline at end of file diff --git a/frappe/public/js/lib/d3.min.js b/frappe/public/js/lib/d3.min.js deleted file mode 100644 index 2856dd2f35..0000000000 --- a/frappe/public/js/lib/d3.min.js +++ /dev/null @@ -1,5 +0,0 @@ -!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function u(n){return!isNaN(n)}function i(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)<0?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)>0?u=i:r=i+1}return r}}}function a(n){return n.length}function o(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function s(n){return(n+="")===xa||n[0]===ba?ba+n:n}function f(n){return(n+="")[0]===ba?n.slice(1):n}function h(n){return s(n)in this._}function g(n){return(n=s(n))in this._&&delete this._[n]}function p(){var n=[];for(var t in this._)n.push(f(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function m(){this._=Object.create(null)}function y(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=_a.length;r>e;++e){var u=_a[e]+t;if(u in n)return u}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,u=-1,i=r.length;++ue;e++)for(var u,i=n[e],a=0,o=i.length;o>a;a++)(u=i[a])&&t(u,a,e);return n}function Z(n){return Sa(n,La),n}function V(n){var t,e;return function(r,u,i){var a,o=n[i].update,l=o.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(a=o[t])&&++t0&&(n=n.slice(0,o));var c=qa.get(n);return c&&(n=c,l=B),o?t?u:r:t?b:i}function $(n,t){return function(e){var r=oa.event;oa.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{oa.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Ra,u="click"+r,i=oa.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ta&&(Ta="onselectstart"in e?!1:x(e.style,"userSelect")),Ta){var a=n(e).style,o=a[Ta];a[Ta]="none"}return function(n){if(i.on(r,null),Ta&&(a[Ta]=o),n){var t=function(){i.on(u,null)};i.on(u,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var u=r.createSVGPoint();if(0>Da){var i=t(n);if(i.scrollX||i.scrollY){r=oa.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var a=r[0][0].getScreenCTM();Da=!(a.f||a.e),r.remove()}}return Da?(u.x=e.pageX,u.y=e.pageY):(u.x=e.clientX,u.y=e.clientY),u=u.matrixTransform(n.getScreenCTM().inverse()),[u.x,u.y]}var o=n.getBoundingClientRect();return[e.clientX-o.left-n.clientLeft,e.clientY-o.top-n.clientTop]}function G(){return oa.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?ja:Math.acos(n)}function tn(n){return n>1?Oa:-1>n?-Oa:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function an(n){return(n=Math.sin(n/2))*n}function on(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(a-i)*n/60:180>n?a:240>n?i+(a-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,a;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,a=.5>=e?e*(1+t):e+t-e*t,i=2*e-a,new yn(u(n+120),u(n),u(n-120))}function sn(n,t,e){return this instanceof sn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof sn?new sn(n.h,n.c,n.l):n instanceof hn?pn(n.l,n.a,n.b):pn((n=Sn((n=oa.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new sn(n,t,e)}function fn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Ia)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof sn?fn(n.h,n.c,n.l):Sn((n=yn(n)).r,n.g,n.b):new hn(n,t,e)}function gn(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=vn(u)*Qa,r=vn(r)*no,i=vn(i)*to,new yn(mn(3.2404542*u-1.5371385*r-.4985314*i),mn(-.969266*u+1.8760108*r+.041556*i),mn(.0556434*u-.2040259*r+1.0572252*i))}function pn(n,t,e){return n>0?new sn(Math.atan2(e,t)*Ya,Math.sqrt(t*t+e*e),n):new sn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function mn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function yn(n,t,e){return this instanceof yn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof yn?new yn(n.r,n.g,n.b):_n(""+n,yn,cn):new yn(n,t,e)}function Mn(n){return new yn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,u,i,a=0,o=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(Nn(u[0]),Nn(u[1]),Nn(u[2]))}return(i=uo.get(n))?t(i.r,i.g,i.b):(null==n||"#"!==n.charAt(0)||isNaN(i=parseInt(n.slice(1),16))||(4===n.length?(a=(3840&i)>>4,a=a>>4|a,o=240&i,o=o>>4|o,l=15&i,l=l<<4|l):7===n.length&&(a=(16711680&i)>>16,o=(65280&i)>>8,l=255&i)),t(a,o,l))}function wn(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),a=Math.max(n,t,e),o=a-i,l=(a+i)/2;return o?(u=.5>l?o/(a+i):o/(2-a-i),r=n==a?(t-e)/o+(e>t?6:0):t==a?(e-n)/o+2:(n-t)/o+4,r*=60):(r=NaN,u=l>0&&1>l?0:r),new ln(r,u,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/Qa),u=dn((.2126729*n+.7151522*t+.072175*e)/no),i=dn((.0193339*n+.119192*t+.9503041*e)/to);return hn(116*u-16,500*(r-u),200*(u-i))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function u(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(i,l)}catch(r){return void a.error.call(i,r)}a.load.call(i,n)}else a.error.call(i,l)}var i={},a=oa.dispatch("beforesend","progress","load","error"),o={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=u:l.onreadystatechange=function(){l.readyState>3&&u()},l.onprogress=function(n){var t=oa.event;oa.event=n;try{a.progress.call(i,l)}finally{oa.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?o[n]:(null==t?delete o[n]:o[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(c=n,i):c},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(ca(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),l.open(e,n,!0),null==t||"accept"in o||(o.accept=t+",*/*"),l.setRequestHeader)for(var s in o)l.setRequestHeader(s,o[s]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),a.beforesend.call(i,l),l.send(null==r?null:r),i},i.abort=function(){return l.abort(),i},oa.rebind(i,a,"on"),null==r?i:i.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,n:null};return ao?ao.n=i:io=i,ao=i,oo||(lo=clearTimeout(lo),oo=1,co(Tn)),i}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(lo),lo=setTimeout(Tn,t)),oo=0):(oo=1,co(Tn))}function Rn(){for(var n=Date.now(),t=io;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=io,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r&&e?function(n,t){for(var u=n.length,i=[],a=0,o=r[0],l=0;u>0&&o>0&&(l+o+1>t&&(o=Math.max(1,t-l)),i.push(n.substring(u-=o,u+o)),!((l+=o+1)>t));)o=r[a=(a+1)%r.length];return i.reverse().join(e)}:y;return function(n){var e=fo.exec(n),r=e[1]||" ",a=e[2]||">",o=e[3]||"-",l=e[4]||"",c=e[5],s=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1,y=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===a)&&(c=r="0",a="="),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+g.toLowerCase());case"c":y=!1;case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===l&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=ho.get(g)||Fn;var M=c&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===o?"":o;if(0>p){var l=oa.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=p;n=g(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=y?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&f&&(x=i(x,1/0));var S=v.length+x.length+b.length+(M?0:u.length),k=s>S?new Array(S=s-S+1).join(r):"";return M&&(x=i(k+x,k.length?s-b.length:1/0)),u+=v,n=x+b,("<"===a?u+n+k:">"===a?k+u+n:"^"===a?k.substring(0,S>>=1)+u+n+k.substring(S):u+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new po(e-1)),1),e}function i(n,e){return t(n=new po(+n),e),n}function a(n,r,i){var a=u(n),o=[];if(i>1)for(;r>a;)e(a)%i||o.push(new Date(+a)),t(a,1);else for(;r>a;)o.push(new Date(+a)),t(a,1);return o}function o(n,t,e){try{po=Hn;var r=new Hn;return r._=n,a(r,t,e)}finally{po=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=a;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(u),l.offset=In(i),l.range=o,n}function In(n){return function(t,e){try{po=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{po=Date}}}function Yn(n){function t(n){function t(t){for(var e,u,i,a=[],o=-1,l=0;++oo;){if(r>=c)return-1;if(u=t.charCodeAt(o++),37===u){if(a=t.charAt(o++),i=C[a in mo?t.charAt(o++):a],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function s(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{po=Hn;var t=new po;return t._=n,r(t)}finally{po=Date}}var r=t(n);return e.parse=function(n){try{po=Hn;var t=r.parse(n);return t&&t._}finally{po=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=oa.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(m),k=Xn(m),N=Vn(y),E=Xn(y);p.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+go.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return p[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(go.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(go.mondayOfYear(n),t,2)},x:t(h),X:t(g),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:ot,"%":function(){return"%"}},C={a:r,A:u,b:i,B:a,c:o,d:tt,e:tt,H:rt,I:rt,j:et,L:at,m:nt,M:ut,p:s,S:it,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function Vn(n){return new RegExp("^(?:"+n.map(oa.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function ut(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function it(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function at(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ot(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=Ma(t)/60|0,u=Ma(t)%60;return e+Zn(r,"0",2)+Zn(u,"0",2)}function lt(n,t,e){Mo.lastIndex=0;var r=Mo.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,o=a*e,l=Math.cos(t),c=Math.sin(t),s=i*c,f=u*l+s*Math.cos(o),h=s*a*Math.sin(o);ko.add(Math.atan2(h,f)),r=n,u=l,i=c}var t,e,r,u,i;No.point=function(a,o){No.point=n,r=(t=a)*Ia,u=Math.cos(o=(e=o)*Ia/2+ja/4),i=Math.sin(o)},No.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function mt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function yt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return Ma(n[0]-t[0])o;++o)u.point((e=n[o])[0],e[1]);return void u.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,i.push(l),a.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,i.push(l),a.push(c)}}),a.sort(t),qt(i),qt(a),i.length){for(var o=0,l=e,c=a.length;c>o;++o)a[o].e=l=!l;for(var s,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;s=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var o=0,c=s.length;c>o;++o)u.point((f=s[o])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){s=g.p.z;for(var o=s.length-1;o>=0;--o)u.point((f=s[o])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,s=g.z,p=!p}while(!g.v);u.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r0){for(b||(i.polygonStart(),b=!0),i.lineStart();++a1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Dt))}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:a,lineStart:l,lineEnd:c,polygonStart:function(){y.point=s,y.lineStart=f,y.lineEnd=h,g=[],p=[]},polygonEnd:function(){y.point=a,y.lineStart=l,y.lineEnd=c,g=oa.merge(g);var n=Ot(m,p);g.length?(b||(i.polygonStart(),b=!0),Lt(g,Ut,n,e,i)):n&&(b||(i.polygonStart(),b=!0),i.lineStart(),e(null,null,1,i),i.lineEnd()),b&&(i.polygonEnd(),b=!1),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},M=Pt(),x=t(M),b=!1;return y}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Oa-Pa:Oa-n[1])-((t=t.x)[0]<0?t[1]-Oa-Pa:Oa-t[1])}function jt(n){var t,e=NaN,r=NaN,u=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(i,a){var o=i>0?ja:-ja,l=Ma(i-e);Ma(l-ja)0?Oa:-Oa),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(o,r),n.point(i,r),t=0):u!==o&&l>=ja&&(Ma(e-u)Pa?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*a)):(t+r)/2}function Ht(n,t,e,r){var u;if(null==n)u=e*Oa,r.point(-ja,u),r.point(0,u),r.point(ja,u),r.point(ja,0),r.point(ja,-u),r.point(0,-u),r.point(-ja,-u),r.point(-ja,0),r.point(-ja,u);else if(Ma(n[0]-t[0])>Pa){var i=n[0]o;++o){var c=t[o],s=c.length;if(s)for(var f=c[0],h=f[0],g=f[1]/2+ja/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===s&&(d=0),n=c[d];var m=n[0],y=n[1]/2+ja/4,M=Math.sin(y),x=Math.cos(y),b=m-h,_=b>=0?1:-1,w=_*b,S=w>ja,k=p*M;if(ko.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),i+=S?b+_*Fa:b,S^h>=e^m>=e){var N=yt(dt(f),dt(n));bt(N);var E=yt(u,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(a+=S^b>=0?1:-1)}if(!d++)break;h=m,p=M,v=x,f=n}}return(-Pa>i||Pa>i&&0>ko)^1&a}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>i}function e(n){var e,i,l,c,s;return{lineStart:function(){c=l=!1,s=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=a?v?0:u(f,h):v?u(f+(0>f?ja:-ja),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(g=r(e,p),(wt(e,g)||wt(p,g))&&(p[0]+=Pa,p[1]+=Pa,v=t(p[0],p[1]))),v!==l)s=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(o&&e&&a^v){var m;d&i||!(m=r(p,e,!0))||(s=0,a?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&wt(e,p)||n.point(p[0],p[1]),e=p,l=v,i=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return s|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),u=dt(t),a=[1,0,0],o=yt(r,u),l=mt(o,o),c=o[0],s=l-c*c;if(!s)return!e&&n;var f=i*l/s,h=-i*c/s,g=yt(a,o),p=xt(a,f),v=xt(o,h);Mt(p,v);var d=g,m=mt(p,d),y=mt(d,d),M=m*m-y*(mt(p,p)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-m-x)/y);if(Mt(b,p),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=Ma(E-ja)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(Ma(b[0]-w)ja^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-m+x)/y);return Mt(z,p),[b,_t(z)]}}}function u(t,e){var r=a?n:ja-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),a=i>0,o=Ma(i)>Pa,l=ve(n,6*Ia);return Rt(t,e,l,a?[0,-n]:[-ja,n-ja])}function Yt(n,t,e,r){return function(u){var i,a=u.a,o=u.b,l=a.x,c=a.y,s=o.x,f=o.y,h=0,g=1,p=s-l,v=f-c;if(i=n-l,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-l,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-c,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-c,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:l+h*p,y:c+h*v}),1>g&&(u.b={x:l+g*p,y:c+g*v}),u}}}}}}function Zt(n,t,e,r){function u(r,u){return Ma(r[0]-n)0?0:3:Ma(r[0]-e)0?2:1:Ma(r[1]-t)0?1:0:u>0?3:2}function i(n,t){return a(n.x,t.x)}function a(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(o){function l(n){for(var t=0,e=d.length,r=n[1],u=0;e>u;++u)for(var i,a=1,o=d[u],l=o.length,c=o[0];l>a;++a)i=o[a],c[1]<=r?i[1]>r&&Q(c,i,n)>0&&++t:i[1]<=r&&Q(c,i,n)<0&&--t,c=i;return 0!==t}function c(i,o,l,c){var s=0,f=0;if(null==i||(s=u(i,l))!==(f=u(o,l))||a(i,o)<0^l>0){do c.point(0===s||3===s?n:e,s>1?r:t);while((s=(s+l+4)%4)!==f)}else c.point(o[0],o[1])}function s(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function f(n,t){s(n,t)&&o.point(n,t)}function h(){C.point=p,d&&d.push(m=[]),S=!0,w=!1,b=_=NaN}function g(){v&&(p(y,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=f,w&&o.lineEnd()}function p(n,t){n=Math.max(-Ho,Math.min(Ho,n)),t=Math.max(-Ho,Math.min(Ho,t));var e=s(n,t);if(d&&m.push([n,t]),S)y=n,M=t,x=e,S=!1,e&&(o.lineStart(),o.point(n,t));else if(e&&w)o.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(o.lineStart(),o.point(r.a.x,r.a.y)),o.point(r.b.x,r.b.y),e||o.lineEnd(),k=!1):e&&(o.lineStart(),o.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,m,y,M,x,b,_,w,S,k,N=o,E=Pt(),A=Yt(n,t,e,r),C={point:f,lineStart:h,lineEnd:g,polygonStart:function(){o=E,v=[],d=[],k=!0},polygonEnd:function(){o=N,v=oa.merge(v);var t=l([n,r]),e=k&&t,u=v.length;(e||u)&&(o.polygonStart(),e&&(o.lineStart(),c(null,null,1,o),o.lineEnd()),u&&Lt(v,i,t,c,o),o.polygonEnd()),v=d=m=null}};return C}}function Vt(n){var t=0,e=ja/3,r=oe(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*ja/180,e=n[1]*ja/180):[t/ja*180,e/ja*180]},u}function Xt(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),a-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),a=Math.sqrt(i)/u;return e.invert=function(n,t){var e=a-t;return[Math.atan2(n,e)/u,tn((i-(n*n+e*e)*u*u)/(2*u))]},e}function $t(){function n(n,t){Io+=u*n-r*t,r=n,u=t}var t,e,r,u;$o.point=function(i,a){$o.point=n,t=r=i,e=u=a},$o.lineEnd=function(){n(t,e)}}function Bt(n,t){Yo>n&&(Yo=n),n>Vo&&(Vo=n),Zo>t&&(Zo=t),t>Xo&&(Xo=t)}function Wt(){function n(n,t){a.push("M",n,",",t,i)}function t(n,t){a.push("M",n,",",t),o.point=e}function e(n,t){a.push("L",n,",",t)}function r(){o.point=n}function u(){a.push("Z")}var i=Jt(4.5),a=[],o={point:n,lineStart:function(){o.point=t},lineEnd:r,polygonStart:function(){o.lineEnd=u},polygonEnd:function(){o.lineEnd=r,o.point=n},pointRadius:function(n){return i=Jt(n),o},result:function(){if(a.length){var n=a.join("");return a=[],n}}};return o}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Co+=n,zo+=t,++Lo}function Kt(){function n(n,r){var u=n-t,i=r-e,a=Math.sqrt(u*u+i*i);qo+=a*(t+n)/2,To+=a*(e+r)/2,Ro+=a,Gt(t=n,e=r)}var t,e;Wo.point=function(r,u){Wo.point=n,Gt(t=r,e=u)}}function Qt(){Wo.point=Gt}function ne(){function n(n,t){var e=n-r,i=t-u,a=Math.sqrt(e*e+i*i);qo+=a*(r+n)/2,To+=a*(u+t)/2,Ro+=a,a=u*n-r*t,Do+=a*(r+n),Po+=a*(u+t),Uo+=3*a,Gt(r=n,u=t)}var t,e,r,u;Wo.point=function(i,a){Wo.point=n,Gt(t=r=i,e=u=a)},Wo.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+a,e),n.arc(t,e,a,0,Fa)}function e(t,e){n.moveTo(t,e),o.point=r}function r(t,e){n.lineTo(t,e)}function u(){o.point=t}function i(){n.closePath()}var a=4.5,o={point:t,lineStart:function(){o.point=e},lineEnd:u,polygonStart:function(){o.lineEnd=i},polygonEnd:function(){o.lineEnd=u,o.point=t},pointRadius:function(n){return a=n,o},result:b};return o}function ee(n){function t(n){return(o?r:e)(n)}function e(t){return ie(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=i,t.lineStart()}function i(e,r){var i=dt([e,r]),a=n(e,r);u(M,x,y,b,_,w,M=a[0],x=a[1],y=e,b=i[0],_=i[1],w=i[2],o,t),t.point(M,x)}function a(){S.point=e,t.lineEnd()}function l(){ -r(),S.point=c,S.lineEnd=s}function c(n,t){i(f=n,h=t),g=M,p=x,v=b,d=_,m=w,S.point=i}function s(){u(M,x,y,b,_,w,g,p,f,v,d,m,o,t),S.lineEnd=a,a()}var f,h,g,p,v,d,m,y,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:a,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,o,l,c,s,f,h,g,p,v,d,m){var y=s-t,M=f-e,x=y*y+M*M;if(x>4*i&&d--){var b=o+g,_=l+p,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=Ma(Ma(w)-1)i||Ma((y*z+M*L)/x-.5)>.3||a>o*g+l*p+c*v)&&(u(t,e,r,o,l,c,A,C,N,b/=S,_/=S,w,d,m),m.point(A,C),u(A,C,N,b,_,w,s,f,h,g,p,v,d,m))}}var i=.5,a=Math.cos(30*Ia),o=16;return t.precision=function(n){return arguments.length?(o=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function re(n){var t=ee(function(t,e){return n([t*Ya,e*Ya])});return function(n){return le(t(n))}}function ue(n){this.stream=n}function ie(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function ae(n){return oe(function(){return n})()}function oe(n){function t(n){return n=o(n[0]*Ia,n[1]*Ia),[n[0]*h+l,c-n[1]*h]}function e(n){return n=o.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Ya,n[1]*Ya]}function r(){o=Ct(a=fe(m,M,x),i);var n=i(v,d);return l=g-n[0]*h,c=p+n[1]*h,u()}function u(){return s&&(s.valid=!1,s=null),t}var i,a,o,l,c,s,f=ee(function(n,t){return n=i(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,M=0,x=0,b=Fo,_=y,w=null,S=null;return t.stream=function(n){return s&&(s.valid=!1),s=le(b(a,f(_(n)))),s.valid=!0,s},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fo):It((w=+n)*Ia),u()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):y,u()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Ia,d=n[1]%360*Ia,r()):[v*Ya,d*Ya]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Ia,M=n[1]%360*Ia,x=n.length>2?n[2]%360*Ia:0,r()):[m*Ya,M*Ya,x*Ya]},oa.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function le(n){return ie(n,function(t,e){n.point(t*Ia,e*Ia)})}function ce(n,t){return[n,t]}function se(n,t){return[n>ja?n-Fa:-ja>n?n+Fa:n,t]}function fe(n,t,e){return n?t||e?Ct(ge(n),pe(t,e)):ge(n):t||e?pe(t,e):se}function he(n){return function(t,e){return t+=n,[t>ja?t-Fa:-ja>t?t+Fa:t,e]}}function ge(n){var t=he(n);return t.invert=he(-n),t}function pe(n,t){function e(n,t){var e=Math.cos(t),o=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),s=c*r+o*u;return[Math.atan2(l*i-s*a,o*r-c*u),tn(s*i+l*a)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),a=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),o=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),s=c*i-l*a;return[Math.atan2(l*i+c*a,o*r+s*u),tn(s*r-o*u)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,a,o){var l=a*t;null!=u?(u=de(e,u),i=de(e,i),(a>0?i>u:u>i)&&(u+=a*Fa)):(u=n+a*Fa,i=n-.5*l);for(var c,s=u;a>0?s>i:i>s;s-=l)o.point((c=_t([e,-r*Math.cos(s),-r*Math.sin(s)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Pa)%(2*Math.PI)}function me(n,t,e){var r=oa.range(n,t-Pa,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function ye(n,t,e){var r=oa.range(n,t-Pa,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),a=Math.cos(r),o=Math.sin(r),l=u*Math.cos(n),c=u*Math.sin(n),s=a*Math.cos(e),f=a*Math.sin(e),h=2*Math.asin(Math.sqrt(an(r-t)+u*a*an(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*l+t*s,u=e*c+t*f,a=e*i+t*o;return[Math.atan2(u,r)*Ya,Math.atan2(a,Math.sqrt(r*r+u*u))*Ya]}:function(){return[n*Ya,t*Ya]};return p.distance=h,p}function _e(){function n(n,u){var i=Math.sin(u*=Ia),a=Math.cos(u),o=Ma((n*=Ia)-t),l=Math.cos(o);Jo+=Math.atan2(Math.sqrt((o=a*Math.sin(o))*o+(o=r*i-e*a*l)*o),e*i+r*a*l),t=n,e=i,r=a}var t,e,r;Go.point=function(u,i){t=u*Ia,e=Math.sin(i*=Ia),r=Math.cos(i),Go.point=n},Go.lineEnd=function(){Go.point=Go.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),a=Math.cos(u);return[Math.atan2(n*i,r*a),Math.asin(r&&e*i/r)]},e}function Se(n,t){function e(n,t){a>0?-Oa+Pa>t&&(t=-Oa+Pa):t>Oa-Pa&&(t=Oa-Pa);var e=a/Math.pow(u(t),i);return[e*Math.sin(i*n),a-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(ja/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),a=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=a-t,r=K(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(a/r,1/i))-Oa]},e):Ne}function ke(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return Ma(u)u;u++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[u])<=0;)--r;e[r++]=u}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var u=n[0],i=e[0],a=t[0]-u,o=r[0]-i,l=n[1],c=e[1],s=t[1]-l,f=r[1]-c,h=(o*(l-c)-f*(u-i))/(f*a-o*s);return[u+h*a,l+h*s]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),al.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,a=n.N,o=[n];je(n);for(var l=i;l.circle&&Ma(e-l.circle.x)s;++s)c=o[s],l=o[s-1],nr(c.edge,l.site,c.site,u);l=o[0],c=o[f-1],c.edge=Ke(l.site,c.site,null,u),$e(l),$e(c)}function He(n){for(var t,e,r,u,i=n.x,a=n.y,o=al._;o;)if(r=Oe(o,a)-i,r>Pa)o=o.L;else{if(u=i-Ie(o,a),!(u>Pa)){r>-Pa?(t=o.P,e=o):u>-Pa?(t=o,e=o.N):t=e=o;break}if(!o.R){t=o;break}o=o.R}var l=Ue(n);if(al.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),al.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,s=c.x,f=c.y,h=n.x-s,g=n.y-f,p=e.site,v=p.x-s,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,M=v*v+d*d,x={x:(d*y-g*M)/m+s,y:(h*M-v*y)/m+f};nr(e.edge,c,p,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,p,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var a=n.P;if(!a)return-(1/0);e=a.site;var o=e.x,l=e.y,c=l-t;if(!c)return o;var s=o-r,f=1/i-1/c,h=s/c;return f?(-h+Math.sqrt(h*h-2*f*(s*s/(-2*c)-l+c/2+u-i/2)))/f+r:(r+o)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,u,i,a,o,l,c,s,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=il,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(o=i.edges,l=o.length,a=0;l>a;)s=o[a].end(),r=s.x,u=s.y,c=o[++a%l].start(),t=c.x,e=c.y,(Ma(r-t)>Pa||Ma(u-e)>Pa)&&(o.splice(a,0,new tr(Qe(i.site,s,Ma(r-f)Pa?{x:f,y:Ma(t-f)Pa?{x:Ma(e-p)Pa?{x:h,y:Ma(t-h)Pa?{x:Ma(e-g)=-Ua)){var g=l*l+c*c,p=s*s+f*f,v=(f*g-c*p)/h,d=(l*p-s*g)/h,f=d+o,m=sl.pop()||new Xe;m.arc=n,m.site=u,m.x=v+a,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,M=ll._;M;)if(m.yd||d>=o)return;if(h>p){if(i){if(i.y>=c)return}else i={x:d,y:l};e={x:d,y:c}}else{if(i){if(i.yr||r>1)if(h>p){if(i){if(i.y>=c)return}else i={x:(l-u)/r,y:l};e={x:(c-u)/r,y:c}}else{if(i){if(i.yg){if(i){if(i.x>=o)return}else i={x:a,y:r*a+u};e={x:o,y:r*o+u}}else{if(i){if(i.xi||f>a||r>h||u>g)){if(p=n.point){var p,v=t-n.x,d=e-n.y,m=v*v+d*d;if(l>m){var y=Math.sqrt(l=m);r=t-y,u=e-y,i=t+y,a=e+y,o=p}}for(var M=n.nodes,x=.5*(s+h),b=.5*(f+g),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,s,f,x,b);break;case 1:c(n,x,f,h,b);break;case 2:c(n,s,b,x,g);break;case 3:c(n,x,b,h,g)}}}(n,r,u,i,a),o}function vr(n,t){n=oa.rgb(n),t=oa.rgb(t);var e=n.r,r=n.g,u=n.b,i=t.r-e,a=t.g-r,o=t.b-u;return function(n){return"#"+bn(Math.round(e+i*n))+bn(Math.round(r+a*n))+bn(Math.round(u+o*n))}}function dr(n,t){var e,r={},u={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):u[e]=n[e];for(e in t)e in n||(u[e]=t[e]);return function(n){for(e in r)u[e]=r[e](n);return u}}function mr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function yr(n,t){var e,r,u,i=hl.lastIndex=gl.lastIndex=0,a=-1,o=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=gl.exec(t));)(u=r.index)>i&&(u=t.slice(i,u),o[a]?o[a]+=u:o[++a]=u),(e=e[0])===(r=r[0])?o[a]?o[a]+=r:o[++a]=r:(o[++a]=null,l.push({i:a,x:mr(e,r)})),i=gl.lastIndex;return ir;++r)o[(e=l[r]).i]=e.x(n);return o.join("")})}function Mr(n,t){for(var e,r=oa.interpolators.length;--r>=0&&!(e=oa.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],u=[],i=n.length,a=t.length,o=Math.min(n.length,t.length);for(e=0;o>e;++e)r.push(Mr(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;a>e;++e)u[e]=t[e];return function(n){for(e=0;o>e;++e)u[e]=r[e](n);return u}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Oa)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Fa*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Fa/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=oa.hcl(n),t=oa.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,a=t.c-r,o=t.l-u;return isNaN(a)&&(a=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return fn(e+i*n,r+a*n,u+o*n)+""}}function Dr(n,t){n=oa.hsl(n),t=oa.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,a=t.s-r,o=t.l-u;return isNaN(a)&&(a=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return cn(e+i*n,r+a*n,u+o*n)+""}}function Pr(n,t){n=oa.lab(n),t=oa.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,a=t.a-r,o=t.b-u;return function(n){return gn(e+i*n,r+a*n,u+o*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),u=Fr(t,e),i=Hr(Or(e,t,-u))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:mr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:mr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var u=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:u-4,x:mr(n[0],t[0])},{i:u-2,x:mr(n[1],t[1])})}else(1!==t[0]||1!==t[1])&&e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=oa.transform(n),t=oa.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,u=-1,i=r.length;++u=0;)e.push(u[r])}function au(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(i=n.children)&&(u=i.length))for(var u,i,a=-1;++ae;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function mu(n){return n.reduce(yu,0)}function yu(n,t){return n+t[1]}function Mu(n,t){return xu(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xu(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function bu(n){return[oa.min(n),oa.max(n)]}function _u(n,t){return n.value-t.value}function wu(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Su(n,t){n._pack_next=t,t._pack_prev=n}function ku(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function Nu(n){function t(n){s=Math.min(n.x-n.r,s),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(c=e.length)){var e,r,u,i,a,o,l,c,s=1/0,f=-(1/0),h=1/0,g=-(1/0);if(e.forEach(Eu),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(u=e[1],u.x=u.r,u.y=0,t(u),c>2))for(i=e[2],zu(r,u,i),t(i),wu(r,i),r._pack_prev=i,wu(i,u),u=r._pack_next,a=3;c>a;a++){zu(r,u,i=e[a]);var p=0,v=1,d=1;for(o=u._pack_next;o!==u;o=o._pack_next,v++)if(ku(o,i)){p=1;break}if(1==p)for(l=r._pack_prev;l!==o._pack_prev&&!ku(l,i);l=l._pack_prev,d++);p?(d>v||v==d&&u.ra;a++)i=e[a],i.x-=m,i.y-=y,M=Math.max(M,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=M,e.forEach(Au)}}function Eu(n){n._pack_next=n._pack_prev=n}function Au(n){delete n._pack_next,delete n._pack_prev}function Cu(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,a=u.length;++i=0;)t=u[i],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pu(n,t,e){return n.a.parent===t.parent?n.a:e}function Uu(n){return 1+oa.max(n,function(n){return n.y})}function ju(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fu(n){var t=n.children;return t&&t.length?Fu(t[0]):n}function Hu(n){var t,e=n.children;return e&&(t=e.length)?Hu(e[t-1]):n}function Ou(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Iu(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Yu(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zu(n){return n.rangeExtent?n.rangeExtent():Yu(n.range())}function Vu(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function Xu(n,t){var e,r=0,u=n.length-1,i=n[r],a=n[u];return i>a&&(e=r,r=u,u=e,e=i,i=a,a=e),n[r]=t.floor(i),n[u]=t.ceil(a),n}function $u(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bu(n,t,e,r){var u=[],i=[],a=0,o=Math.min(n.length,t.length)-1;for(n[o]2?Bu:Vu,l=r?Wr:Br;return a=u(n,t,l,e),o=u(t,n,l,Mr),i}function i(n){return a(n)}var a,o;return i.invert=function(n){return o(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(Ur)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return Qu(n,t)},i.tickFormat=function(t,e){return ni(n,t,e)},i.nice=function(t){return Gu(n,t),u()},i.copy=function(){return Wu(n,t,e,r)},u()}function Ju(n,t){return oa.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gu(n,t){return Xu(n,$u(Ku(n,t)[2])),Xu(n,$u(Ku(n,t)[2])),n}function Ku(n,t){null==t&&(t=10);var e=Yu(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function Qu(n,t){return oa.range.apply(oa,Ku(n,t))}function ni(n,t,e){var r=Ku(n,t);if(e){var u=fo.exec(e);if(u.shift(),"s"===u[8]){var i=oa.formatPrefix(Math.max(Ma(r[0]),Ma(r[1])));return u[7]||(u[7]="."+ti(i.scale(r[2]))),u[8]="f",e=oa.format(u.join("")),function(n){return e(i.scale(n))+i.symbol}}u[7]||(u[7]="."+ei(u[8],r)),e=u.join("")}else e=",."+ti(r[2])+"f";return oa.format(e)}function ti(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function ei(n,t){var e=ti(t[2]);return n in kl?Math.abs(e-ti(Math.max(Ma(t[0]),Ma(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ri(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function a(t){return n(u(t))}return a.invert=function(t){return i(n.invert(t))},a.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),a):r},a.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),a):t},a.nice=function(){var t=Xu(r.map(u),e?Math:El);return n.domain(t),r=t.map(i),a},a.ticks=function(){var n=Yu(r),a=[],o=n[0],l=n[1],c=Math.floor(u(o)),s=Math.ceil(u(l)),f=t%1?2:t;if(isFinite(s-c)){if(e){for(;s>c;c++)for(var h=1;f>h;h++)a.push(i(c)*h);a.push(i(c))}else for(a.push(i(c));c++0;h--)a.push(i(c)*h);for(c=0;a[c]l;s--);a=a.slice(c,s)}return a},a.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=oa.format(e));var r=Math.max(1,t*n/a.ticks().length);return function(n){var a=n/i(Math.round(u(n)));return t-.5>a*t&&(a*=t),r>=a?e(n):""}},a.copy=function(){return ri(n.copy(),t,e,r)},Ju(a,n)}function ui(n,t,e){function r(t){return n(u(t))}var u=ii(t),i=ii(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return Qu(e,n)},r.tickFormat=function(n,t){return ni(e,n,t)},r.nice=function(n){return r.domain(Gu(e,n))},r.exponent=function(a){return arguments.length?(u=ii(t=a),i=ii(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return ui(n.copy(),t,e)},Ju(r,n)}function ii(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ai(n,t){function e(e){return i[((u.get(e)||("range"===t.t?u.set(e,n.push(e)):NaN))-1)%i.length]}function r(t,e){return oa.range(n.length).map(function(n){return t+e*n})}var u,i,a;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new c;for(var i,a=-1,o=r.length;++ae?[NaN,NaN]:[e>0?o[e-1]:n[0],et?NaN:t/i+n,[t,t+1/i]},r.copy=function(){return li(n,t,e)},u()}function ci(n,t){function e(e){return e>=e?t[oa.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return ci(n,t)},e}function si(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qu(n,t)},t.tickFormat=function(t,e){return ni(n,t,e)},t.copy=function(){return si(n)},t}function fi(){return 0}function hi(n){return n.innerRadius}function gi(n){return n.outerRadius}function pi(n){return n.startAngle}function vi(n){return n.endAngle}function di(n){return n&&n.padAngle}function mi(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function yi(n,t,e,r,u){var i=n[0]-t[0],a=n[1]-t[1],o=(u?r:-r)/Math.sqrt(i*i+a*a),l=o*a,c=-o*i,s=n[0]+l,f=n[1]+c,h=t[0]+l,g=t[1]+c,p=(s+h)/2,v=(f+g)/2,d=h-s,m=g-f,y=d*d+m*m,M=e-r,x=s*g-h*f,b=(0>m?-1:1)*Math.sqrt(Math.max(0,M*M*y-x*x)),_=(x*m-d*b)/y,w=(-x*d-m*b)/y,S=(x*m+d*b)/y,k=(-x*d+m*b)/y,N=_-p,E=w-v,A=S-p,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mi(n){function t(t){function a(){c.push("M",i(n(s),o))}for(var l,c=[],s=[],f=-1,h=t.length,g=En(e),p=En(r);++f1?n.join("L"):n+"Z"}function bi(n){return n.join("L")+"Z"}function _i(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1&&u.push("H",r[0]),u.join("")}function wi(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1){o=t[1],i=n[l],l++,r+="C"+(u[0]+a[0])+","+(u[1]+a[1])+","+(i[0]-o[0])+","+(i[1]-o[1])+","+i[0]+","+i[1];for(var c=2;c9&&(u=3*t/Math.sqrt(u),a[o]=u*e,a[o+1]=u*r));for(o=-1;++o<=l;)u=(n[Math.min(l,o+1)][0]-n[Math.max(0,o-1)][0])/(6*(1+a[o]*a[o])),i.push([u||0,a[o]*u||0]);return i}function Fi(n){return n.length<3?xi(n):n[0]+Ai(n,ji(n))}function Hi(n){for(var t,e,r,u=-1,i=n.length;++u=t?a(n-t):void(s.c=a)}function a(e){var u=p.active,i=p[u];i&&(i.timer.c=null,i.timer.t=NaN,--p.count,delete p[u],i.event&&i.event.interrupt.call(n,n.__data__,i.index));for(var a in p)if(r>+a){var c=p[a];c.timer.c=null,c.timer.t=NaN,--p.count,delete p[a]}s.c=o,qn(function(){return s.c&&o(e||1)&&(s.c=null,s.t=NaN),1},0,l),p.active=r,v.event&&v.event.start.call(n,n.__data__,t),g=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&g.push(r)}),h=v.ease,f=v.duration}function o(u){for(var i=u/f,a=h(i),o=g.length;o>0;)g[--o].call(n,a);return i>=1?(v.event&&v.event.end.call(n,n.__data__,t),--p.count?delete p[r]:delete n[e],1):void 0}var l,s,f,h,g,p=n[e]||(n[e]={active:0,count:0}),v=p[r];v||(l=u.time,s=qn(i,0,l),v=p[r]={tween:new c,time:l,timer:s,delay:u.delay,duration:u.duration,ease:u.ease,index:t},u=null,++p.count)}function na(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function ta(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function ea(n){return n.toISOString()}function ra(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=oa.bisect(Kl,u);return i==Kl.length?[t.year,Ku(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/Kl[i-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=ua(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=ua(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yu(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],ua(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ra(n.copy(),t,e)},Ju(r,n)}function ua(n){return new Date(n)}function ia(n){return JSON.parse(n.responseText)}function aa(n){var t=sa.createRange();return t.selectNode(sa.body),t.createContextualFragment(n.responseText)}var oa={version:"3.5.16"},la=[].slice,ca=function(n){return la.call(n)},sa=this.document;if(sa)try{ca(sa.documentElement.childNodes)[0].nodeType}catch(fa){ca=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),sa)try{sa.createElement("DIV").style.setProperty("opacity",0,"")}catch(ha){var ga=this.Element.prototype,pa=ga.setAttribute,va=ga.setAttributeNS,da=this.CSSStyleDeclaration.prototype,ma=da.setProperty;ga.setAttribute=function(n,t){pa.call(this,n,t+"")},ga.setAttributeNS=function(n,t,e){va.call(this,n,t,e+"")},da.setProperty=function(n,t,e){ma.call(this,n,t+"",e)}}oa.ascending=e,oa.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},oa.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ur&&(e=r)}else{for(;++u=r){e=r;break}for(;++ur&&(e=r)}return e},oa.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ue&&(e=r)}else{for(;++u=r){e=r;break}for(;++ue&&(e=r)}return e},oa.extent=function(n,t){var e,r,u,i=-1,a=n.length;if(1===arguments.length){for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}else{for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}return[e,u]},oa.sum=function(n,t){var e,r=0,i=n.length,a=-1;if(1===arguments.length)for(;++a1?l/(s-1):void 0},oa.deviation=function(){var n=oa.variance.apply(this,arguments);return n?Math.sqrt(n):n};var ya=i(e);oa.bisectLeft=ya.left,oa.bisect=oa.bisectRight=ya.right,oa.bisector=function(n){return i(1===n.length?function(t,r){return e(n(t),r)}:n)},oa.shuffle=function(n,t,e){(i=arguments.length)<3&&(e=n.length,2>i&&(t=0));for(var r,u,i=e-t;i;)u=Math.random()*i--|0,r=n[i+t],n[i+t]=n[u+t],n[u+t]=r;return n},oa.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},oa.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},oa.transpose=function(n){if(!(u=n.length))return[];for(var t=-1,e=oa.min(n,a),r=new Array(e);++t=0;)for(r=n[u],t=r.length;--t>=0;)e[--a]=r[t];return e};var Ma=Math.abs;oa.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,u=[],i=o(Ma(e)),a=-1;if(n*=i,t*=i,e*=i,0>e)for(;(r=n+e*++a)>t;)u.push(r/i);else for(;(r=n+e*++a)=i.length)return r?r.call(u,a):e?a.sort(e):a;for(var l,s,f,h,g=-1,p=a.length,v=i[o++],d=new c;++g=i.length)return n;var r=[],u=a[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,u={},i=[],a=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(oa.map,e,0),0)},u.key=function(n){return i.push(n),u},u.sortKeys=function(n){return a[i.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},oa.set=function(n){var t=new m;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(m,{has:h,add:function(n){return this._[s(n+="")]=!0,n},remove:g,values:p,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,f(t))}}),oa.behavior={},oa.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},oa.event=null,oa.requote=function(n){return n.replace(wa,"\\$&")};var wa=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,Sa={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},ka=function(n,t){return t.querySelector(n)},Na=function(n,t){return t.querySelectorAll(n)},Ea=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ea=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(ka=function(n,t){return Sizzle(n,t)[0]||null},Na=Sizzle,Ea=Sizzle.matchesSelector),oa.selection=function(){return oa.select(sa.documentElement)};var Aa=oa.selection.prototype=[];Aa.select=function(n){var t,e,r,u,i=[];n=A(n);for(var a=-1,o=this.length;++a=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),za.hasOwnProperty(e)?{space:za[e],local:n}:n}},Aa.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=oa.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Aa.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,u=-1;if(t=e.classList){for(;++uu){if("string"!=typeof n){2>u&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>u){var i=this.node();return t(i).getComputedStyle(i,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Aa.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Aa.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Aa.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Aa.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Aa.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Aa.remove=function(){return this.each(F)},Aa.data=function(n,t){function e(n,e){var r,u,i,a=n.length,f=e.length,h=Math.min(a,f),g=new Array(f),p=new Array(f),v=new Array(a);if(t){var d,m=new c,y=new Array(a);for(r=-1;++rr;++r)p[r]=H(e[r]);for(;a>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,o.push(p),l.push(g),s.push(v)}var r,u,i=-1,a=this.length;if(!arguments.length){for(n=new Array(a=(r=this[0]).length);++ii;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var o=0,l=e.length;l>o;o++)(r=e[o])&&n.call(r,r.__data__,o,i)&&t.push(r)}return E(u)},Aa.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},Aa.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},Aa.size=function(){var n=0;return Y(this,function(){++n}),n};var La=[];oa.selection.enter=Z,oa.selection.enter.prototype=La,La.append=Aa.append,La.empty=Aa.empty,La.node=Aa.node,La.call=Aa.call,La.size=Aa.size,La.select=function(n){for(var t,e,r,u,i,a=[],o=-1,l=this.length;++or){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var qa=oa.map({mouseenter:"mouseover",mouseleave:"mouseout"});sa&&qa.forEach(function(n){"on"+n in sa&&qa.remove(n)});var Ta,Ra=0;oa.mouse=function(n){return J(n,k())};var Da=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;oa.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,u=0,i=t.length;i>u;++u)if((r=t[u]).identifier===e)return J(n,r)},oa.behavior.drag=function(){function n(){this.on("mousedown.drag",i).on("touchstart.drag",a)}function e(n,t,e,i,a){return function(){function o(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],p|=n|e,M=r,g({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(m.on(i+d,null).on(a+d,null),y(p),g({type:"dragend"}))}var c,s=this,f=oa.event.target.correspondingElement||oa.event.target,h=s.parentNode,g=r.of(s,arguments),p=0,v=n(),d=".drag"+(null==v?"":"-"+v),m=oa.select(e(f)).on(i+d,o).on(a+d,l),y=W(f),M=t(h,v);u?(c=u.apply(s,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],g({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),u=null,i=e(b,oa.mouse,t,"mousemove","mouseup"),a=e(G,oa.touch,y,"touchmove","touchend");return n.origin=function(t){return arguments.length?(u=t,n):u},oa.rebind(n,r,"on")},oa.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?ca(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Pa=1e-6,Ua=Pa*Pa,ja=Math.PI,Fa=2*ja,Ha=Fa-Pa,Oa=ja/2,Ia=ja/180,Ya=180/ja,Za=Math.SQRT2,Va=2,Xa=4;oa.interpolateZoom=function(n,t){var e,r,u=n[0],i=n[1],a=n[2],o=t[0],l=t[1],c=t[2],s=o-u,f=l-i,h=s*s+f*f;if(Ua>h)r=Math.log(c/a)/Za,e=function(n){return[u+n*s,i+n*f,a*Math.exp(Za*n*r)]};else{var g=Math.sqrt(h),p=(c*c-a*a+Xa*h)/(2*a*Va*g),v=(c*c-a*a-Xa*h)/(2*c*Va*g),d=Math.log(Math.sqrt(p*p+1)-p),m=Math.log(Math.sqrt(v*v+1)-v);r=(m-d)/Za,e=function(n){var t=n*r,e=rn(d),o=a/(Va*g)*(e*un(Za*t+d)-en(d));return[u+o*s,i+o*f,a*e/rn(Za*t+d)]}}return e.duration=1e3*r,e},oa.behavior.zoom=function(){function n(n){n.on(L,f).on(Ba+".zoom",g).on("dblclick.zoom",p).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function u(n){k.k=Math.max(A[0],Math.min(A[1],n))}function i(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function a(t,e,r,a){t.__chart__={x:k.x,y:k.y,k:k.k},u(Math.pow(2,a)),i(d=e,r),t=oa.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function o(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){o(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function s(n){--z||(n({type:"zoomend"}),d=null)}function f(){function n(){o=1,i(oa.mouse(u),h),c(a)}function r(){f.on(q,null).on(T,null),g(o),s(a)}var u=this,a=D.of(u,arguments),o=0,f=oa.select(t(u)).on(q,n).on(T,r),h=e(oa.mouse(u)),g=W(u);Il.call(u),l(a)}function h(){function n(){var n=oa.touches(p);return g=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=oa.event.target;oa.select(t).on(x,r).on(b,o),_.push(t);for(var e=oa.event.changedTouches,u=0,i=e.length;i>u;++u)d[e[u].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var s=l[0];a(p,s,d[s.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var s=l[0],f=l[1],h=s[0]-f[0],g=s[1]-f[1];m=h*h+g*g}}function r(){var n,t,e,r,a=oa.touches(p);Il.call(p);for(var o=0,l=a.length;l>o;++o,r=null)if(e=a[o],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var s=(s=e[0]-n[0])*s+(s=e[1]-n[1])*s,f=m&&Math.sqrt(s/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],u(f*g)}M=null,i(n,t),c(v)}function o(){if(oa.event.touches.length){for(var t=oa.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var u in d)return void n()}oa.selectAll(_).on(y,null),w.on(L,f).on(R,h),N(),s(v)}var g,p=this,v=D.of(p,arguments),d={},m=0,y=".zoom-"+oa.event.changedTouches[0].identifier,x="touchmove"+y,b="touchend"+y,_=[],w=oa.select(p),N=W(p);t(),l(v),w.on(L,null).on(R,t)}function g(){var n=D.of(this,arguments);y?clearTimeout(y):(Il.call(this),v=e(d=m||oa.mouse(this)),l(n)),y=setTimeout(function(){y=null,s(n)},50),S(),u(Math.pow(2,.002*$a())*k.k),i(d,v),c(n)}function p(){var n=oa.mouse(this),t=Math.log(k.k)/Math.LN2;a(this,n,e(n),oa.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,m,y,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Wa,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Ba||(Ba="onwheel"in sa?($a=function(){return-oa.event.deltaY*(oa.event.deltaMode?120:1)},"wheel"):"onmousewheel"in sa?($a=function(){return oa.event.wheelDelta},"mousewheel"):($a=function(){return-oa.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?oa.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],u=d?d[0]:e/2,i=d?d[1]:r/2,a=oa.interpolateZoom([(u-k.x)/k.k,(i-k.y)/k.k,e/k.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=a(t),o=e/r[2];this.__chart__=k={x:u-r[0]*o,y:i-r[1]*o,k:o},c(n)}}).each("interrupt.zoom",function(){s(n)}).each("end.zoom",function(){s(n)}):(this.__chart__=k,l(n),c(n),s(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},o(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},u(+t),o(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Wa:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(m=t&&[+t[0],+t[1]],n):m},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},oa.rebind(n,D,"on")};var $a,Ba,Wa=[0,1/0];oa.color=on,on.prototype.toString=function(){return this.rgb()+""},oa.hsl=ln;var Ja=ln.prototype=new on;Ja.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Ja.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Ja.rgb=function(){return cn(this.h,this.s,this.l)},oa.hcl=sn;var Ga=sn.prototype=new on;Ga.brighter=function(n){return new sn(this.h,this.c,Math.min(100,this.l+Ka*(arguments.length?n:1)))},Ga.darker=function(n){return new sn(this.h,this.c,Math.max(0,this.l-Ka*(arguments.length?n:1)))},Ga.rgb=function(){return fn(this.h,this.c,this.l).rgb()},oa.lab=hn;var Ka=18,Qa=.95047,no=1,to=1.08883,eo=hn.prototype=new on;eo.brighter=function(n){return new hn(Math.min(100,this.l+Ka*(arguments.length?n:1)),this.a,this.b)},eo.darker=function(n){return new hn(Math.max(0,this.l-Ka*(arguments.length?n:1)),this.a,this.b)},eo.rgb=function(){return gn(this.l,this.a,this.b)},oa.rgb=yn;var ro=yn.prototype=new on;ro.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),new yn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new yn(u,u,u)},ro.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new yn(n*this.r,n*this.g,n*this.b)},ro.hsl=function(){return wn(this.r,this.g,this.b)},ro.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var uo=oa.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});uo.forEach(function(n,t){uo.set(n,Mn(t))}),oa.functor=En,oa.xhr=An(y),oa.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var a=Cn(n,t,null==e?r:u(e),i);return a.row=function(n){return arguments.length?a.response(null==(e=n)?r:u(n)):e},a}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function i(t){return t.map(a).join(n)}function a(n){return o.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var o=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(s>=c)return a;if(u)return u=!1,i;var t=s;if(34===n.charCodeAt(t)){for(var e=t;e++s;){var r=n.charCodeAt(s++),o=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(s)&&(++s,++o);else if(r!==l)continue;return n.slice(t,s-o)}return n.slice(t)}for(var r,u,i={},a={},o=[],c=n.length,s=0,f=0;(r=e())!==a;){for(var h=[];r!==i&&r!==a;)h.push(r),r=e();t&&null==(h=t(h,f++))||o.push(h)}return o},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new m,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(a).join(n)].concat(t.map(function(t){return u.map(function(n){return a(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(i).join("\n")},e},oa.csv=oa.dsv(",","text/csv"),oa.tsv=oa.dsv(" ","text/tab-separated-values");var io,ao,oo,lo,co=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};oa.timer=function(){qn.apply(this,arguments)},oa.timer.flush=function(){Rn(),Dn()},oa.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var so=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);oa.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=oa.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),so[8+e/3]};var fo=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,ho=oa.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=oa.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),go=oa.time={},po=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){vo.setUTCDate.apply(this._,arguments)},setDay:function(){vo.setUTCDay.apply(this._,arguments)},setFullYear:function(){vo.setUTCFullYear.apply(this._,arguments)},setHours:function(){vo.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){vo.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){vo.setUTCMinutes.apply(this._,arguments)},setMonth:function(){vo.setUTCMonth.apply(this._,arguments)},setSeconds:function(){vo.setUTCSeconds.apply(this._,arguments)},setTime:function(){vo.setTime.apply(this._,arguments)}};var vo=Date.prototype;go.year=On(function(n){return n=go.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),go.years=go.year.range,go.years.utc=go.year.utc.range,go.day=On(function(n){var t=new po(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),go.days=go.day.range,go.days.utc=go.day.utc.range,go.dayOfYear=function(n){var t=go.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=go[n]=On(function(n){return(n=go.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=go.year(n).getDay();return Math.floor((go.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});go[n+"s"]=e.range,go[n+"s"].utc=e.utc.range,go[n+"OfYear"]=function(n){var e=go.year(n).getDay();return Math.floor((go.dayOfYear(n)+(e+t)%7)/7)}}),go.week=go.sunday,go.weeks=go.sunday.range,go.weeks.utc=go.sunday.utc.range,go.weekOfYear=go.sundayOfYear;var mo={"-":"",_:" ",0:"0"},yo=/^\s*\d+/,Mo=/^%/;oa.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xo=oa.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], -shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});oa.format=xo.numberFormat,oa.geo={},st.prototype={s:0,t:0,add:function(n){ft(n,this.t,bo),ft(bo.s,this.s,this),this.s?this.t+=bo.t:this.s=bo.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var bo=new st;oa.geo.stream=function(n,t){n&&_o.hasOwnProperty(n.type)?_o[n.type](n,t):ht(n,t)};var _o={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++rn?4*ja+n:n,No.lineStart=No.lineEnd=No.point=b}};oa.geo.bounds=function(){function n(n,t){M.push(x=[s=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=dt([t*Ia,e*Ia]);if(m){var u=yt(m,r),i=[u[1],-u[0],0],a=yt(i,u);bt(a),a=_t(a);var l=t-p,c=l>0?1:-1,v=a[0]*Ya*c,d=Ma(l)>180;if(d^(v>c*p&&c*t>v)){var y=a[1]*Ya;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>c*p&&c*t>v)){var y=-a[1]*Ya;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t):h>=s?(s>t&&(s=t),t>h&&(h=t)):t>p?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t)}else n(t,e);m=r,p=t}function e(){b.point=t}function r(){x[0]=s,x[1]=h,b.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=Ma(r)>180?r+(r>0?360:-360):r}else v=n,d=e;No.point(n,e),t(n,e)}function i(){No.lineStart()}function a(){u(v,d),No.lineEnd(),Ma(y)>Pa&&(s=-(h=180)),x[0]=s,x[1]=h,m=null}function o(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nko?(s=-(h=180),f=-(g=90)):y>Pa?g=90:-Pa>y&&(f=-90),x[0]=s,x[1]=h}};return function(n){g=h=-(s=f=1/0),M=[],oa.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,u=M[0],i=[u];t>r;++r)e=M[r],c(e[0],u)||c(e[1],u)?(o(u[0],e[1])>o(u[0],u[1])&&(u[1]=e[1]),o(e[0],u[1])>o(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var a,e,p=-(1/0),t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(a=o(u[1],e[0]))>p&&(p=a,s=e[0],h=u[1])}return M=x=null,s===1/0||f===1/0?[[NaN,NaN],[NaN,NaN]]:[[s,f],[h,g]]}}(),oa.geo.centroid=function(n){Eo=Ao=Co=zo=Lo=qo=To=Ro=Do=Po=Uo=0,oa.geo.stream(n,jo);var t=Do,e=Po,r=Uo,u=t*t+e*e+r*r;return Ua>u&&(t=qo,e=To,r=Ro,Pa>Ao&&(t=Co,e=zo,r=Lo),u=t*t+e*e+r*r,Ua>u)?[NaN,NaN]:[Math.atan2(e,t)*Ya,tn(r/Math.sqrt(u))*Ya]};var Eo,Ao,Co,zo,Lo,qo,To,Ro,Do,Po,Uo,jo={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){jo.lineStart=At},polygonEnd:function(){jo.lineStart=Nt}},Fo=Rt(zt,jt,Ht,[-ja,-ja/2]),Ho=1e9;oa.geo.clipExtent=function(){var n,t,e,r,u,i,a={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(o){return arguments.length?(i=Zt(n=+o[0][0],t=+o[0][1],e=+o[1][0],r=+o[1][1]),u&&(u.valid=!1,u=null),a):[[n,t],[e,r]]}};return a.extent([[0,0],[960,500]])},(oa.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,oa.geo.albers=function(){return oa.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},oa.geo.albersUsa=function(){function n(n){var i=n[0],a=n[1];return t=null,e(i,a),t||(r(i,a),t)||u(i,a),t}var t,e,r,u,i=oa.geo.albers(),a=oa.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),o=oa.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?a:u>=.166&&.234>u&&r>=-.214&&-.115>r?o:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=a.stream(n),r=o.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),a.precision(t),o.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),a.scale(.35*t),o.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var c=i.scale(),s=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[s-.455*c,f-.238*c],[s+.455*c,f+.238*c]]).stream(l).point,r=a.translate([s-.307*c,f+.201*c]).clipExtent([[s-.425*c+Pa,f+.12*c+Pa],[s-.214*c-Pa,f+.234*c-Pa]]).stream(l).point,u=o.translate([s-.205*c,f+.212*c]).clipExtent([[s-.214*c+Pa,f+.166*c+Pa],[s-.115*c-Pa,f+.234*c-Pa]]).stream(l).point,n},n.scale(1070)};var Oo,Io,Yo,Zo,Vo,Xo,$o={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Io=0,$o.lineStart=$t},polygonEnd:function(){$o.lineStart=$o.lineEnd=$o.point=b,Oo+=Ma(Io/2)}},Bo={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wo={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wo.lineStart=ne},polygonEnd:function(){Wo.point=Gt,Wo.lineStart=Kt,Wo.lineEnd=Qt}};oa.geo.path=function(){function n(n){return n&&("function"==typeof o&&i.pointRadius(+o.apply(this,arguments)),a&&a.valid||(a=u(i)),oa.geo.stream(n,a)),i.result()}function t(){return a=null,n}var e,r,u,i,a,o=4.5;return n.area=function(n){return Oo=0,oa.geo.stream(n,u($o)),Oo},n.centroid=function(n){return Co=zo=Lo=qo=To=Ro=Do=Po=Uo=0,oa.geo.stream(n,u(Wo)),Uo?[Do/Uo,Po/Uo]:Ro?[qo/Ro,To/Ro]:Lo?[Co/Lo,zo/Lo]:[NaN,NaN]},n.bounds=function(n){return Vo=Xo=-(Yo=Zo=1/0),oa.geo.stream(n,u(Bo)),[[Yo,Zo],[Vo,Xo]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||re(n):y,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new Wt:new te(n),"function"!=typeof o&&i.pointRadius(o),t()):r},n.pointRadius=function(t){return arguments.length?(o="function"==typeof t?t:(i.pointRadius(+t),+t),n):o},n.projection(oa.geo.albersUsa()).context(null)},oa.geo.transform=function(n){return{stream:function(t){var e=new ue(t);for(var r in n)e[r]=n[r];return e}}},ue.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},oa.geo.projection=ae,oa.geo.projectionMutator=oe,(oa.geo.equirectangular=function(){return ae(ce)}).raw=ce.invert=ce,oa.geo.rotation=function(n){function t(t){return t=n(t[0]*Ia,t[1]*Ia),t[0]*=Ya,t[1]*=Ya,t}return n=fe(n[0]%360*Ia,n[1]*Ia,n.length>2?n[2]*Ia:0),t.invert=function(t){return t=n.invert(t[0]*Ia,t[1]*Ia),t[0]*=Ya,t[1]*=Ya,t},t},se.invert=ce,oa.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=fe(-n[0]*Ia,-n[1]*Ia,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Ya,n[1]*=Ya}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Ia,u*Ia),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Ia,(u=+r)*Ia),n):u},n.angle(90)},oa.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Ia,u=n[1]*Ia,i=t[1]*Ia,a=Math.sin(r),o=Math.cos(r),l=Math.sin(u),c=Math.cos(u),s=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*a)*e+(e=c*s-l*f*o)*e),l*s+c*f*o)},oa.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return oa.range(Math.ceil(i/d)*d,u,d).map(h).concat(oa.range(Math.ceil(c/m)*m,l,m).map(g)).concat(oa.range(Math.ceil(r/p)*p,e,p).filter(function(n){return Ma(n%d)>Pa}).map(s)).concat(oa.range(Math.ceil(o/v)*v,a,v).filter(function(n){return Ma(n%m)>Pa}).map(f))}var e,r,u,i,a,o,l,c,s,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(l).slice(1),h(u).reverse().slice(1),g(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],c=+t[0][1],l=+t[1][1],i>u&&(t=i,i=u,u=t),c>l&&(t=c,c=l,l=t),n.precision(y)):[[i,c],[u,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],o=+t[0][1],a=+t[1][1],r>e&&(t=r,r=e,e=t),o>a&&(t=o,o=a,a=t),n.precision(y)):[[r,o],[e,a]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,s=me(o,a,90),f=ye(r,e,y),h=me(c,l,90),g=ye(i,u,y),n):y},n.majorExtent([[-180,-90+Pa],[180,90-Pa]]).minorExtent([[-180,-80-Pa],[180,80+Pa]])},oa.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=Me,u=xe;return n.distance=function(){return oa.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},oa.geo.interpolate=function(n,t){return be(n[0]*Ia,n[1]*Ia,t[0]*Ia,t[1]*Ia)},oa.geo.length=function(n){return Jo=0,oa.geo.stream(n,Go),Jo};var Jo,Go={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ko=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(oa.geo.azimuthalEqualArea=function(){return ae(Ko)}).raw=Ko;var Qo=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},y);(oa.geo.azimuthalEquidistant=function(){return ae(Qo)}).raw=Qo,(oa.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(oa.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(oa.geo.gnomonic=function(){return ae(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Oa]},(oa.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(oa.geo.orthographic=function(){return ae(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(oa.geo.stereographic=function(){return ae(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Oa]},(oa.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,oa.geom={},oa.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u=En(e),i=En(r),a=n.length,o=[],l=[];for(t=0;a>t;t++)o.push([+u.call(this,n[t],t),+i.call(this,n[t],t),t]);for(o.sort(qe),t=0;a>t;t++)l.push([o[t][0],-o[t][1]]);var c=Le(o),s=Le(l),f=s[0]===c[0],h=s[s.length-1]===c[c.length-1],g=[];for(t=c.length-1;t>=0;--t)g.push(n[o[c[t]][2]]);for(t=+f;t=r&&c.x<=i&&c.y>=u&&c.y<=a?[[r,a],[i,a],[i,u],[r,u]]:[];s.point=n[o]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/Pa)*Pa,y:Math.round(a(n,t)/Pa)*Pa,i:t}})}var r=Ce,u=ze,i=r,a=u,o=fl;return n?t(n):(t.links=function(n){return or(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return or(e(n)).cells.forEach(function(e,r){for(var u,i,a=e.site,o=e.edges.sort(Ve),l=-1,c=o.length,s=o[c-1].edge,f=s.l===a?s.r:s.l;++l=c,h=r>=s,g=h<<1|f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=hr()),f?u=c:o=c,h?a=s:l=s,i(n,t,e,r,u,a,o,l)}var s,f,h,g,p,v,d,m,y,M=En(o),x=En(l);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,a)for(g=0;p>g;++g)s=n[g],s.xm&&(m=s.x),s.y>y&&(y=s.y),f.push(s.x),h.push(s.y);else for(g=0;p>g;++g){var b=+M(s=n[g],g),_=+x(s,g);v>b&&(v=b),d>_&&(d=_),b>m&&(m=b),_>y&&(y=_),f.push(b),h.push(_)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=hr();if(k.add=function(n){i(k,n,+M(n,++g),+x(n,g),v,d,m,y)},k.visit=function(n){gr(n,k,v,d,m,y)},k.find=function(n){return pr(k,n[0],n[1],v,d,m,y)},g=-1,null==t){for(;++g=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||pl,r=dl.get(r)||y,br(r(e.apply(null,la.call(arguments,1))))},oa.interpolateHcl=Rr,oa.interpolateHsl=Dr,oa.interpolateLab=Pr,oa.interpolateRound=Ur,oa.transform=function(n){var t=sa.createElementNS(oa.ns.prefix.svg,"g");return(oa.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:ml)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var ml={a:1,b:0,c:0,d:1,e:0,f:0};oa.interpolateTransform=$r,oa.layout={},oa.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++eo*o/m){if(v>l){var c=t.charge/l;n.px-=i*c,n.py-=a*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=i*c,n.py-=a*c}}return!t.charge}}function t(n){n.px=oa.event.x,n.py=oa.event.y,l.resume()}var e,r,u,i,a,o,l={},c=oa.dispatch("start","tick","end"),s=[1,1],f=.9,h=yl,g=Ml,p=-30,v=xl,d=.1,m=.64,M=[],x=[];return l.tick=function(){if((u*=.99)<.005)return e=null,c.end({type:"end",alpha:u=0}),!0;var t,r,l,h,g,v,m,y,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,g=l.target,y=g.x-h.x,b=g.y-h.y,(v=y*y+b*b)&&(v=u*a[r]*((v=Math.sqrt(v))-i[r])/v,y*=v,b*=v,g.x-=y*(m=h.weight+g.weight?h.weight/(h.weight+g.weight):.5),g.y-=b*m,h.x+=y*(m=1-m),h.y+=b*m);if((m=u*d)&&(y=s[0]/2,b=s[1]/2,r=-1,m))for(;++r<_;)l=M[r],l.x+=(y-l.x)*m,l.y+=(b-l.y)*m;if(p)for(ru(t=oa.geom.quadtree(M),u,o),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*f,l.y-=(l.py-(l.py=l.y))*f);c.tick({type:"tick",alpha:u})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(s=n,l):s},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.friction=function(n){return arguments.length?(f=+n,l):f},l.charge=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(m=n*n,l):Math.sqrt(m)},l.alpha=function(n){return arguments.length?(n=+n,u?n>0?u=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:u=0})):n>0&&(c.start({type:"start",alpha:u=n}),e=qn(l.tick)),l):u},l.start=function(){function n(n,r){if(!e){for(e=new Array(u),l=0;u>l;++l)e[l]=[];for(l=0;c>l;++l){var i=x[l];e[i.source.index].push(i.target),e[i.target.index].push(i.source)}}for(var a,o=e[t],l=-1,s=o.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;u>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",f)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(i=[],"function"==typeof h)for(t=0;c>t;++t)i[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)i[t]=h;if(a=[],"function"==typeof g)for(t=0;c>t;++t)a[t]=+g.call(this,x[t],t);else for(t=0;c>t;++t)a[t]=g;if(o=[],"function"==typeof p)for(t=0;u>t;++t)o[t]=+p.call(this,M[t],t);else for(t=0;u>t;++t)o[t]=p;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=oa.behavior.drag().origin(y).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",nu)),arguments.length?void this.on("mouseover.force",tu).on("mouseout.force",eu).call(r):r},oa.rebind(l,c,"on")};var yl=20,Ml=1,xl=1/0;oa.layout.hierarchy=function(){function n(u){var i,a=[u],o=[];for(u.depth=0;null!=(i=a.pop());)if(o.push(i),(c=e.call(n,i,i.depth))&&(l=c.length)){for(var l,c,s;--l>=0;)a.push(s=c[l]),s.parent=i,s.depth=i.depth+1;r&&(i.value=0),i.children=c}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return au(u,function(n){var e,u;t&&(e=n.children)&&e.sort(t),r&&(u=n.parent)&&(u.value+=n.value)}),o}var t=cu,e=ou,r=lu;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(iu(t,function(n){n.children&&(n.value=0)}),au(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},oa.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(a=i.length)){var a,o,l,c=-1;for(r=t.value?r/t.value:0;++cf?-1:1),p=oa.sum(c),v=p?(f-l*g)/p:0,d=oa.range(l),m=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(a[n],a[t])}),d.forEach(function(n){m[n]={data:a[n],value:o=c[n],startAngle:s,endAngle:s+=o*v+g,padAngle:h}}),m}var t=Number,e=bl,r=0,u=Fa,i=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n.padAngle=function(t){return arguments.length?(i=t,n):i},n};var bl={};oa.layout.stack=function(){function n(o,l){if(!(h=o.length))return o;var c=o.map(function(e,r){return t.call(n,e,r)}),s=c.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),a.call(n,t,e)]})}),f=e.call(n,s,l);c=oa.permute(c,f),s=oa.permute(s,f);var h,g,p,v,d=r.call(n,s,l),m=c[0].length;for(p=0;m>p;++p)for(u.call(n,c[0][p],v=d[p],s[0][p][1]),g=1;h>g;++g)u.call(n,c[g][p],v+=s[g-1][p][1],s[g][p][1]);return o}var t=y,e=pu,r=vu,u=gu,i=fu,a=hu;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||pu,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vu,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(a=t,n):a},n.out=function(t){return arguments.length?(u=t,n):u},n};var _l=oa.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(du),i=n.map(mu),a=oa.range(r).sort(function(n,t){return u[n]-u[t]}),o=0,l=0,c=[],s=[];for(t=0;r>t;++t)e=a[t],l>o?(o+=i[e],c.push(e)):(l+=i[e],s.push(e));return s.reverse().concat(c)},reverse:function(n){return oa.range(n.length).reverse()},"default":pu}),wl=oa.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,a=[],o=0,l=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>o&&(o=r),a.push(r)}for(e=0;i>e;++e)l[e]=(o-a[e])/2;return l},wiggle:function(n){var t,e,r,u,i,a,o,l,c,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=l=c=0,e=1;h>e;++e){for(t=0,u=0;s>t;++t)u+=n[t][e][1];for(t=0,i=0,o=f[e][0]-f[e-1][0];s>t;++t){for(r=0,a=(n[t][e][1]-n[t][e-1][1])/(2*o);t>r;++r)a+=(n[r][e][1]-n[r][e-1][1])/o;i+=a*n[t][e][1]}g[e]=l-=u?i/u*o:0,c>l&&(c=l)}for(e=0;h>e;++e)g[e]-=c;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,a=1/u,o=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=a}for(e=0;i>e;++e)o[e]=0;return o},zero:vu});oa.layout.histogram=function(){function n(n,i){for(var a,o,l=[],c=n.map(e,this),s=r.call(this,c,i),f=u.call(this,s,c,i),i=-1,h=c.length,g=f.length-1,p=t?1:1/h;++i0)for(i=-1;++i=s[0]&&o<=s[1]&&(a=l[oa.bisect(f,o,1,g)-1],a.y+=p,a.push(n[i]));return l}var t=!0,e=Number,r=bu,u=Mu;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return xu(n,t)}:En(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},oa.layout.pack=function(){function n(n,i){var a=e.call(this,n,i),o=a[0],l=u[0],c=u[1],s=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(o.x=o.y=0,au(o,function(n){n.r=+s(n.value)}),au(o,Nu),r){var f=r*(t?1:Math.max(2*o.r/l,2*o.r/c))/2;au(o,function(n){n.r+=f}),au(o,Nu),au(o,function(n){n.r-=f})}return Cu(o,l/2,c/2,t?1:1/Math.max(2*o.r/l,2*o.r/c)),a}var t,e=oa.layout.hierarchy().sort(_u),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},uu(n,e)},oa.layout.tree=function(){function n(n,u){var s=a.call(this,n,u),f=s[0],h=t(f);if(au(h,e),h.parent.m=-h.z,iu(h,r),c)iu(f,i);else{var g=f,p=f,v=f;iu(f,function(n){n.xp.x&&(p=n),n.depth>v.depth&&(v=n)});var d=o(g,p)/2-g.x,m=l[0]/(p.x+o(p,g)/2+d),y=l[1]/(v.depth||1);iu(f,function(n){n.x=(n.x+d)*m,n.y=n.depth*y})}return s}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var u,i=t.children,a=0,o=i.length;o>a;++a)r.push((i[a]=u={_:i[a],parent:t,children:(u=i[a].children)&&u.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:a}).a=u);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Du(n);var i=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+o(n._,r._),n.m=n.z-i):n.z=i}else r&&(n.z=r.z+o(n._,r._));n.parent.A=u(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function u(n,t,e){if(t){for(var r,u=n,i=n,a=t,l=u.parent.children[0],c=u.m,s=i.m,f=a.m,h=l.m;a=Tu(a),u=qu(u),a&&u;)l=qu(l),i=Tu(i),i.a=n,r=a.z+f-u.z-c+o(a._,u._),r>0&&(Ru(Pu(a,n,e),n,r),c+=r,s+=r),f+=a.m,c+=u.m,h+=l.m,s+=i.m;a&&!Tu(i)&&(i.t=a,i.m+=f-s),u&&!qu(l)&&(l.t=u,l.m+=c-h,e=n)}return e}function i(n){n.x*=l[0],n.y=n.depth*l[1]}var a=oa.layout.hierarchy().sort(null).value(null),o=Lu,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(o=t,n):o},n.size=function(t){return arguments.length?(c=null==(l=t)?i:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:i,n):c?l:null},uu(n,a)},oa.layout.cluster=function(){function n(n,i){var a,o=t.call(this,n,i),l=o[0],c=0;au(l,function(n){var t=n.children;t&&t.length?(n.x=ju(t),n.y=Uu(t)):(n.x=a?c+=e(n,a):0,n.y=0,a=n)});var s=Fu(l),f=Hu(l),h=s.x-e(s,f)/2,g=f.x+e(f,s)/2;return au(l,u?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),o}var t=oa.layout.hierarchy().sort(null).value(null),e=Lu,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},uu(n,t)},oa.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++ut?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var a,o,l,c=f(e),s=[],h=i.slice(),p=1/0,v="slice"===g?c.dx:"dice"===g?c.dy:"slice-dice"===g?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),s.area=0;(l=h.length)>0;)s.push(a=h[l-1]),s.area+=a.area,"squarify"!==g||(o=r(s,v))<=p?(h.pop(),p=o):(s.area-=s.pop().area,u(s,v,c,!1),v=Math.min(c.dx,c.dy),s.length=s.area=0,p=1/0);s.length&&(u(s,v,c,!0),s.length=s.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,a=f(t),o=r.slice(),l=[];for(n(o,a.dx*a.dy/t.value),l.area=0;i=o.pop();)l.push(i),l.area+=i.area,null!=i.z&&(u(l,i.z?a.dx:a.dy,a,!o.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,a=-1,o=n.length;++ae&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,a=n.length,o=e.x,c=e.y,s=t?l(n.area/t):0; -if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++ie.dx)&&(s=e.dx);++ie&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=oa.random.normal.apply(oa,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=oa.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},oa.scale={};var Sl={floor:y,ceil:y};oa.scale.linear=function(){return Wu([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};oa.scale.log=function(){return ri(oa.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=oa.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};oa.scale.pow=function(){return ui(oa.scale.linear(),1,[0,1])},oa.scale.sqrt=function(){return oa.scale.pow().exponent(.5)},oa.scale.ordinal=function(){return ai([],{t:"range",a:[[]]})},oa.scale.category10=function(){return oa.scale.ordinal().range(Al)},oa.scale.category20=function(){return oa.scale.ordinal().range(Cl)},oa.scale.category20b=function(){return oa.scale.ordinal().range(zl)},oa.scale.category20c=function(){return oa.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);oa.scale.quantile=function(){return oi([],[])},oa.scale.quantize=function(){return li(0,1,[0,1])},oa.scale.threshold=function(){return ci([.5],[0,1])},oa.scale.identity=function(){return si([0,1])},oa.svg={},oa.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),s=a.apply(this,arguments)-Oa,f=o.apply(this,arguments)-Oa,h=Math.abs(f-s),g=s>f?0:1;if(n>c&&(p=c,c=n,n=p),h>=Ha)return t(c,g)+(n?t(n,1-g):"")+"Z";var p,v,d,m,y,M,x,b,_,w,S,k,N=0,E=0,A=[];if((m=(+l.apply(this,arguments)||0)/2)&&(d=i===ql?Math.sqrt(n*n+c*c):+i.apply(this,arguments),g||(E*=-1),c&&(E=tn(d/c*Math.sin(m))),n&&(N=tn(d/n*Math.sin(m)))),c){y=c*Math.cos(s+E),M=c*Math.sin(s+E),x=c*Math.cos(f-E),b=c*Math.sin(f-E);var C=Math.abs(f-s-2*E)<=ja?0:1;if(E&&mi(y,M,x,b)===g^C){var z=(s+f)/2;y=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else y=M=0;if(n){_=n*Math.cos(f-N),w=n*Math.sin(f-N),S=n*Math.cos(s+N),k=n*Math.sin(s+N);var L=Math.abs(s-f+2*N)<=ja?0:1;if(N&&mi(_,w,S,k)===1-g^L){var q=(s+f)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Pa&&(p=Math.min(Math.abs(c-n)/2,+u.apply(this,arguments)))>.001){v=c>n^g?0:1;var T=p,R=p;if(ja>h){var D=null==S?[_,w]:null==x?[y,M]:Re([y,M],[S,k],[x,b],[_,w]),P=y-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(p,(n-O)/(H-1)),T=Math.min(p,(c-O)/(H+1))}if(null!=x){var I=yi(null==S?[_,w]:[S,k],[y,M],c,T,g),Y=yi([x,b],[_,w],c,T,g);p===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-g^mi(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",g," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",y,",",M);if(null!=S){var Z=yi([y,M],[S,k],n,-R,g),V=yi([_,w],null==x?[y,M]:[x,b],n,-R,g);p===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",g^mi(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-g," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",y,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",g," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-g," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hi,r=gi,u=fi,i=ql,a=pi,o=vi,l=di;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(u=En(t),n):u},n.padRadius=function(t){return arguments.length?(i=t==ql?ql:En(t),n):i},n.startAngle=function(t){return arguments.length?(a=En(t),n):a},n.endAngle=function(t){return arguments.length?(o=En(t),n):o},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+a.apply(this,arguments)+ +o.apply(this,arguments))/2-Oa;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";oa.svg.line=function(){return Mi(y)};var Tl=oa.map({linear:xi,"linear-closed":bi,step:_i,"step-before":wi,"step-after":Si,basis:zi,"basis-open":Li,"basis-closed":qi,bundle:Ti,cardinal:Ei,"cardinal-open":ki,"cardinal-closed":Ni,monotone:Fi});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];oa.svg.line.radial=function(){var n=Mi(Hi);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wi.reverse=Si,Si.reverse=wi,oa.svg.area=function(){return Oi(y)},oa.svg.area.radial=function(){var n=Oi(Hi);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},oa.svg.chord=function(){function n(n,o){var l=t(this,i,n,o),c=t(this,a,n,o);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?u(l.r,l.p1,l.r,l.p0):u(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+u(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=o.call(n,u,r),a=l.call(n,u,r)-Oa,s=c.call(n,u,r)-Oa;return{r:i,a0:a,a1:s,p0:[i*Math.cos(a),i*Math.sin(a)],p1:[i*Math.cos(s),i*Math.sin(s)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>ja)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=Me,a=xe,o=Ii,l=pi,c=vi;return n.radius=function(t){return arguments.length?(o=En(t),n):o},n.source=function(t){return arguments.length?(i=En(t),n):i},n.target=function(t){return arguments.length?(a=En(t),n):a},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},oa.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),a=e.call(this,n,u),o=(i.y+a.y)/2,l=[i,{x:i.x,y:o},{x:a.x,y:o},a];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yi;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},oa.svg.diagonal.radial=function(){var n=oa.svg.diagonal(),t=Yi,e=n.projection;return n.projection=function(n){return arguments.length?e(Zi(t=n)):t},n},oa.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$i)(e.call(this,n,r))}var t=Xi,e=Vi;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=oa.map({circle:$i,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});oa.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Ia);Aa.transition=function(n){for(var t,e,r=Hl||++Zl,u=Ki(n),i=[],a=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},o=-1,l=this.length;++oi;i++){u.push(t=[]);for(var e=this[i],o=0,l=e.length;l>o;o++)(r=e[o])&&n.call(r,r.__data__,o,i)&&t.push(r)}return Wi(u,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(u){u[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(o)}function r(){this.removeAttributeNS(o.space,o.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(o);return e!==n&&(t=a(e,n),function(n){this.setAttribute(o,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(o.space,o.local);return e!==n&&(t=a(e,n),function(n){this.setAttributeNS(o.space,o.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var a="transform"==n?$r:Mr,o=oa.ns.qualify(n);return Ji(this,"attr."+n,t,o.local?i:u)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=oa.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Yl.style=function(n,e,r){function u(){this.style.removeProperty(n)}function i(e){return null==e?u:(e+="",function(){var u,i=t(this).getComputedStyle(this,null).getPropertyValue(n);return i!==e&&(u=Mr(i,e),function(t){this.style.setProperty(n,u(t),r)})})}var a=arguments.length;if(3>a){if("string"!=typeof n){2>a&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ji(this,"style."+n,e,i)},Yl.styleTween=function(n,e,r){function u(u,i){var a=e.call(this,u,i,t(this).getComputedStyle(this,null).getPropertyValue(n));return a&&function(t){this.style.setProperty(n,a(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,u)},Yl.text=function(n){return Ji(this,"text",n,Gi)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=oa.ease.apply(oa,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,u,i){r[e][t].delay=+n.call(r,r.__data__,u,i)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,u,i){r[e][t].duration=Math.max(1,n.call(r,r.__data__,u,i))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var u=Ol,i=Hl;try{Hl=e,Y(this,function(t,u,i){Ol=t[r][e],n.call(t,t.__data__,u,i)})}finally{Ol=u,Hl=i}}else Y(this,function(u){var i=u[r][e];(i.event||(i.event=oa.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,u=this.id,i=++Zl,a=this.namespace,o=[],l=0,c=this.length;c>l;l++){o.push(n=[]);for(var t=this[l],s=0,f=t.length;f>s;s++)(e=t[s])&&(r=e[a][u],Qi(e,s,a,i,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wi(o,a,i)},oa.svg.axis=function(){function n(n){n.each(function(){var n,c=oa.select(this),s=this.__chart__||e,f=this.__chart__=e.copy(),h=null==l?f.ticks?f.ticks.apply(f,o):f.domain():l,g=null==t?f.tickFormat?f.tickFormat.apply(f,o):y:t,p=c.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",Pa),d=oa.transition(p.exit()).style("opacity",Pa).remove(),m=oa.transition(p.order()).style("opacity",1),M=Math.max(u,0)+a,x=Zu(f),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),oa.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=m.select("line"),C=p.select("text").text(g),z=v.select("text"),L=m.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=na,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*i+"V0H"+x[1]+"V"+q*i)):(n=ta,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*i+","+x[0]+"H0V"+x[1]+"H"+q*i)),E.attr(N,q*u),z.attr(k,q*M),A.attr(S,0).attr(N,q*u),L.attr(w,0).attr(k,q*M),f.rangeBand){var T=f,R=T.rangeBand()/2;s=f=function(n){return T(n)+R}}else s.rangeBand?s=f:d.call(n,f,s);v.call(n,s,f),m.call(n,f,f)})}var t,e=oa.scale.linear(),r=Vl,u=6,i=6,a=3,o=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(o=ca(arguments),n):o},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(a=+t,n):a},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};oa.svg.brush=function(){function n(t){t.each(function(){var t=oa.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",i).on("touchstart.brush",i),a=t.selectAll(".background").data([0]);a.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var o=t.selectAll(".resize").data(v,y);o.exit().remove(),o.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),o.style("display",n.empty()?"none":null);var l,f=oa.transition(t),h=oa.transition(a);c&&(l=Zu(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(f)),s&&(l=Zu(s),h.attr("y",l[0]).attr("height",l[1]-l[0]),u(f)),e(f)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+f[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",f[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",f[1]-f[0])}function u(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function i(){function i(){32==oa.event.keyCode&&(C||(M=null,L[0]-=f[1],L[1]-=h[1],C=2),S())}function v(){32==oa.event.keyCode&&2==C&&(L[0]+=f[1],L[1]+=h[1],C=0,S())}function d(){var n=oa.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(oa.event.altKey?(M||(M=[(f[0]+f[1])/2,(h[0]+h[1])/2]),L[0]=f[+(n[0]s?(u=r,r=s):u=s),v[0]!=r||v[1]!=u?(e?o=null:a=null,v[0]=r,v[1]=u,!0):void 0}function y(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),oa.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=oa.select(oa.event.target),w=l.of(b,arguments),k=oa.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&s,C=_.classed("extent"),z=W(b),L=oa.mouse(b),q=oa.select(t(b)).on("keydown.brush",i).on("keyup.brush",v);if(oa.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",y):q.on("mousemove.brush",d).on("mouseup.brush",y),k.interrupt().selectAll("*").interrupt(),C)L[0]=f[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[f[1-T]-L[0],h[1-R]-L[1]],L[0]=f[T],L[1]=h[R]}else oa.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),oa.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var a,o,l=N(n,"brushstart","brush","brushend"),c=null,s=null,f=[0,0],h=[0,0],g=!0,p=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:f,y:h,i:a,j:o},e=this.__chart__||t;this.__chart__=t,Hl?oa.select(this).transition().each("start.brush",function(){a=e.i,o=e.j,f=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(f,t.x),r=xr(h,t.y);return a=o=null,function(u){f=t.x=e(u),h=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){a=t.i,o=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!s],n):c},n.y=function(t){return arguments.length?(s=t,v=Bl[!c<<1|!s],n):s},n.clamp=function(t){return arguments.length?(c&&s?(g=!!t[0],p=!!t[1]):c?g=!!t:s&&(p=!!t),n):c&&s?[g,p]:c?g:s?p:null},n.extent=function(t){var e,r,u,i,l;return arguments.length?(c&&(e=t[0],r=t[1],s&&(e=e[0],r=r[0]),a=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),(e!=f[0]||r!=f[1])&&(f=[e,r])),s&&(u=t[0],i=t[1],c&&(u=u[1],i=i[1]),o=[u,i],s.invert&&(u=s(u),i=s(i)),u>i&&(l=u,u=i,i=l),(u!=h[0]||i!=h[1])&&(h=[u,i])),n):(c&&(a?(e=a[0],r=a[1]):(e=f[0],r=f[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),s&&(o?(u=o[0],i=o[1]):(u=h[0],i=h[1],s.invert&&(u=s.invert(u),i=s.invert(i)),u>i&&(l=u,u=i,i=l))),c&&s?[[e,u],[r,i]]:c?[e,r]:s&&[u,i])},n.clear=function(){return n.empty()||(f=[0,0],h=[0,0],a=o=null),n},n.empty=function(){return!!c&&f[0]==f[1]||!!s&&h[0]==h[1]},oa.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=go.format=xo.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?ea:Gl,ea.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},ea.toString=Gl.toString,go.second=On(function(n){return new po(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),go.seconds=go.second.range,go.seconds.utc=go.second.utc.range,go.minute=On(function(n){return new po(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),go.minutes=go.minute.range,go.minutes.utc=go.minute.utc.range,go.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new po(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),go.hours=go.hour.range,go.hours.utc=go.hour.utc.range,go.month=On(function(n){return n=go.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),go.months=go.month.range,go.months.utc=go.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[go.second,1],[go.second,5],[go.second,15],[go.second,30],[go.minute,1],[go.minute,5],[go.minute,15],[go.minute,30],[go.hour,1],[go.hour,3],[go.hour,6],[go.hour,12],[go.day,1],[go.day,2],[go.week,1],[go.month,1],[go.month,3],[go.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return oa.range(Math.ceil(n/e)*e,+t,e).map(ua)},floor:y,ceil:y};Ql.year=go.year,go.scale=function(){return ra(oa.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=go.year.utc,go.scale.utc=function(){return ra(oa.scale.linear(),ec,rc)},oa.text=An(function(n){return n.responseText}),oa.json=function(n,t){return Cn(n,"application/json",ia,t)},oa.html=function(n,t){return Cn(n,"text/html",aa,t)},oa.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=oa,define(oa)):"object"==typeof module&&module.exports?module.exports=oa:this.d3=oa}(); \ No newline at end of file diff --git a/frappe/public/less/graphs.less b/frappe/public/less/charts.less similarity index 92% rename from frappe/public/less/graphs.less rename to frappe/public/less/charts.less index 079fbf17dc..03ff165a32 100644 --- a/frappe/public/less/graphs.less +++ b/frappe/public/less/charts.less @@ -1,6 +1,8 @@ -/* graphs */ -.graph-container { +/* charts */ +.chart-container { + // font-family: Verdana, Geneva, Tahoma, sans-serif; + .graph-focus-margin { margin: 0px 5%; } @@ -46,10 +48,10 @@ .axis, .chart-label { font-size: 10px; - fill: #959ba1; + fill: #555b51; line { - stroke: rgba(27,31,35,0.1); + stroke: rgba(27,31,35,0.2); } } @@ -176,6 +178,9 @@ .stroke.light-green { stroke: #98d85b; } +.stroke.lightgreen { + stroke: #98d85b; +} .stroke.green { stroke: #28a745; } @@ -213,6 +218,9 @@ .fill.light-green { fill: #98d85b; } +.fill.lightgreen { + fill: #98d85b; +} .fill.green { fill: #28a745; } @@ -250,6 +258,9 @@ .background.light-green { background: #98d85b; } +.background.lightgreen { + background: #98d85b; +} .background.green { background: #28a745; } @@ -287,6 +298,9 @@ .border-top.light-green { border-top: 3px solid #98d85b; } +.border-top.lightgreen { + border-top: 3px solid #98d85b; +} .border-top.green { border-top: 3px solid #28a745; } diff --git a/frappe/public/less/desk.less b/frappe/public/less/desk.less index 613db4940d..e1f3b2a8b6 100644 --- a/frappe/public/less/desk.less +++ b/frappe/public/less/desk.less @@ -245,6 +245,10 @@ textarea.form-control { } } +.barcode-wrapper { + text-align: center; +} + @media (min-width: 768px) { .video-modal .modal-dialog { width: 700px; diff --git a/frappe/public/less/desktop.less b/frappe/public/less/desktop.less index f257b4d97a..55bc98a4dd 100644 --- a/frappe/public/less/desktop.less +++ b/frappe/public/less/desktop.less @@ -70,7 +70,7 @@ body[data-route=""] .navbar-default, body[data-route="desktop"] .navbar-default } .app-icon path { - fill: @icon-color; + // fill: @icon-color; transition: 0.2s; -webkit-transition: 0.2s; } @@ -94,7 +94,7 @@ body[data-route=""] .navbar-default, body[data-route="desktop"] .navbar-default } .app-icon:hover path { - fill: @icon-hover; + // fill: @icon-hover; } .app-icon:hover i, diff --git a/frappe/public/less/form.less b/frappe/public/less/form.less index dd2b07988d..3b0ebf0894 100644 --- a/frappe/public/less/form.less +++ b/frappe/public/less/form.less @@ -913,4 +913,15 @@ body[data-route^="Form/Communication"] textarea[data-fieldname="subject"] { height: 80px !important; } +.frappe-control[data-fieldtype="Attach"] { + .attached-file { + position: relative; + margin-top: 5px; + .close { + position: absolute; + top: 0; + right: 0; + } + } +} \ No newline at end of file diff --git a/frappe/public/less/page.less b/frappe/public/less/page.less index 52294214d8..b9222ec835 100644 --- a/frappe/public/less/page.less +++ b/frappe/public/less/page.less @@ -339,6 +339,10 @@ select.input-sm { background-color: #f5f7fa; } +.page-container .page-card-container { + background-color: #fff; +} + .page-card-container { padding: 70px; } diff --git a/frappe/sessions.py b/frappe/sessions.py index c25ccadced..379385ac2f 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -53,7 +53,7 @@ def clear_global_cache(): frappe.model.meta.clear_cache() frappe.cache().delete_value(["app_hooks", "installed_apps", "app_modules", "module_app", "notification_config", 'system_settings' - 'scheduler_events', 'time_zone', 'webhooks']) + 'scheduler_events', 'time_zone', 'webhooks', 'active_domains', 'active_modules']) frappe.setup_module_map() diff --git a/frappe/templates/emails/auto_email_report.html b/frappe/templates/emails/auto_email_report.html index d31ff3e62d..707220ebf4 100644 --- a/frappe/templates/emails/auto_email_report.html +++ b/frappe/templates/emails/auto_email_report.html @@ -38,6 +38,7 @@ {% endif %} +{% if not frappe.db.get_value("System Settings", "System Settings", "hide_footer_in_auto_email_reports")|int %} -
        @@ -48,4 +49,5 @@

        {{ _("Edit Auto Email Report Settings") }}: {{edit_report_settings}}

        \ No newline at end of file + +{% endif %} diff --git a/frappe/test_runner.py b/frappe/test_runner.py index dd1fa8ac39..22656838eb 100644 --- a/frappe/test_runner.py +++ b/frappe/test_runner.py @@ -198,6 +198,11 @@ def _add_test(app, path, filename, verbose, test_suite=None, ui_tests=False): relative_path=relative_path.replace('/', '.'), module_name=filename[:-3]) module = importlib.import_module(module_name) + + if hasattr(module, "test_dependencies"): + for doctype in module.test_dependencies: + make_test_records(doctype, verbose=verbose) + is_ui_test = True if hasattr(module, 'TestDriver') else False if is_ui_test != ui_tests: diff --git a/frappe/tests/test_email.py b/frappe/tests/test_email.py index fc639e77e9..b35b816e7b 100644 --- a/frappe/tests/test_email.py +++ b/frappe/tests/test_email.py @@ -49,7 +49,7 @@ class TestEmail(unittest.TestCase): self.assertTrue('test@example.com' in queue_recipients) self.assertTrue('test1@example.com' in queue_recipients) self.assertEquals(len(queue_recipients), 2) - self.assertTrue('Unsubscribe' in frappe.flags.sent_mail) + self.assertTrue('Unsubscribe' in frappe.flags.sent_mail.decode()) def test_cc_header(self): #test if sending with cc's makes it into header @@ -84,7 +84,7 @@ class TestEmail(unittest.TestCase): self.assertTrue('test@example.com' in queue_recipients) self.assertTrue('test1@example.com' in queue_recipients) - self.assertTrue('This email was sent to test@example.com and copied to test1@example.com' in frappe.flags.sent_mail) + self.assertTrue('This email was sent to test@example.com and copied to test1@example.com' in frappe.flags.sent_mail.decode()) def test_expose(self): from frappe.utils.verified_command import verify_request @@ -104,12 +104,12 @@ class TestEmail(unittest.TestCase): where status='Sent'""", as_dict=1)[0].message self.assertTrue('' in message) - email_obj = email.message_from_string(frappe.flags.sent_mail) + email_obj = email.message_from_string(frappe.flags.sent_mail.decode()) for part in email_obj.walk(): content = part.get_payload(decode=True) if content: - frappe.local.flags.signed_query_string = re.search('(?<=/api/method/frappe.email.queue.unsubscribe\?).*(?=\n)', content).group(0) + frappe.local.flags.signed_query_string = re.search('(?<=/api/method/frappe.email.queue.unsubscribe\?).*(?=\n)', content.decode()).group(0) self.assertTrue(verify_request()) break @@ -150,7 +150,7 @@ class TestEmail(unittest.TestCase): self.assertFalse('test@example.com' in queue_recipients) self.assertTrue('test1@example.com' in queue_recipients) self.assertEquals(len(queue_recipients), 1) - self.assertTrue('Unsubscribe' in frappe.flags.sent_mail) + self.assertTrue('Unsubscribe' in frappe.flags.sent_mail.decode()) def test_email_queue_limit(self): from frappe.email.queue import send, EmailLimitCrossedError diff --git a/frappe/tests/test_goal.py b/frappe/tests/test_goal.py index 6e94858785..20c2a9a399 100644 --- a/frappe/tests/test_goal.py +++ b/frappe/tests/test_goal.py @@ -31,4 +31,4 @@ class TestGoal(unittest.TestCase): frappe.db.set_value('Event', docname, 'description', 1) data = get_monthly_goal_graph_data('Test', 'Event', docname, 'description', 'description', 'description', 'Event', '', 'description', 'creation', 'starts_on = "2014-01-01"', 'count') - self.assertEquals(float(data['y'][0]['values'][-1]), 1) + self.assertEquals(float(data['data']['datasets'][0]['values'][-1]), 1) diff --git a/frappe/tests/ui/data/test_lib.js b/frappe/tests/ui/data/test_lib.js index 71ba61efaa..a6ca8a4628 100644 --- a/frappe/tests/ui/data/test_lib.js +++ b/frappe/tests/ui/data/test_lib.js @@ -1,15 +1,11 @@ frappe.tests = { data: {}, - get_fixture_names: (doctype) => { - return Object.keys(frappe.test_data[doctype]); - }, make: function(doctype, data) { return frappe.run_serially([ () => frappe.set_route('List', doctype), () => frappe.new_doc(doctype), () => { - if (frappe.quick_entry) - { + if (frappe.quick_entry) { frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(); return frappe.timeout(1); } @@ -79,13 +75,13 @@ frappe.tests = { }); return frappe.run_serially(grid_row_tasks); }, - setup_doctype: (doctype) => { + setup_doctype: (doctype, data) => { return frappe.run_serially([ () => frappe.set_route('List', doctype), () => frappe.timeout(1), () => { frappe.tests.data[doctype] = []; - let expected = frappe.tests.get_fixture_names(doctype); + let expected = Object.keys(data); cur_list.data.forEach((d) => { frappe.tests.data[doctype].push(d.name); if(expected.indexOf(d.name) !== -1) { @@ -98,7 +94,7 @@ frappe.tests = { expected.forEach(function(d) { if(d) { tasks.push(() => frappe.tests.make(doctype, - frappe.test_data[doctype][d])); + data[d])); } }); diff --git a/frappe/tests/ui/setup_wizard.js b/frappe/tests/ui/setup_wizard.js deleted file mode 100644 index add10883c3..0000000000 --- a/frappe/tests/ui/setup_wizard.js +++ /dev/null @@ -1,43 +0,0 @@ -var login = require("./login.js")['Login']; - -module.exports = { - before: browser => { - browser - .url(browser.launch_url + '/login') - .waitForElementVisible('body', 5000); - }, - 'Login': login, - 'Welcome': browser => { - let slide_selector = '[data-slide-name="welcome"]'; - browser - .assert.title('Frappe Desk') - .pause(5000) - .assert.visible(slide_selector, 'Check if welcome slide is visible') - .assert.value('select[data-fieldname="language"]', 'English') - .click(slide_selector + ' .next-btn'); - }, - 'Region': browser => { - let slide_selector = '[data-slide-name="region"]'; - browser - .waitForElementVisible(slide_selector , 2000) - .pause(6000) - .setValue('select[data-fieldname="language"]', "India") - .pause(4000) - .assert.containsText('div[data-fieldname="timezone"]', 'India Time - Asia/Kolkata') - .click(slide_selector + ' .next-btn'); - }, - 'User': browser => { - let slide_selector = '[data-slide-name="user"]'; - browser - .waitForElementVisible(slide_selector, 2000) - .pause(3000) - .setValue('input[data-fieldname="full_name"]', "John Doe") - .setValue('input[data-fieldname="email"]', "john@example.com") - .setValue('input[data-fieldname="password"]', "vbjwearghu") - .click(slide_selector + ' .next-btn'); - }, - - after: browser => { - browser.end(); - }, -}; \ No newline at end of file diff --git a/frappe/tests/ui/test_oauth20.py b/frappe/tests/ui/test_oauth20.py index b9b63847e2..c6361fc39d 100644 --- a/frappe/tests/ui/test_oauth20.py +++ b/frappe/tests/ui/test_oauth20.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import unittest, frappe, requests, time from frappe.test_runner import make_test_records from frappe.utils.selenium_testdriver import TestDriver -from six.moves.urllib.parse import urlparse +from six.moves.urllib.parse import urlparse, parse_qs class TestOAuth20(unittest.TestCase): def setUp(self): @@ -18,7 +18,7 @@ class TestOAuth20(unittest.TestCase): frappe.db.set_value("Social Login Keys", None, "frappe_server_url", "http://localhost:8000") frappe.db.commit() - def test_login_to_authorize_url(self): + def test_login_using_authorization_code(self): # Go to Authorize url self.driver.get( @@ -70,3 +70,46 @@ class TestOAuth20(unittest.TestCase): self.assertTrue(bearer_token.get("refresh_token")) self.assertTrue(bearer_token.get("scope")) self.assertTrue(bearer_token.get("token_type") == "Bearer") + + def test_login_using_implicit_token(self): + + oauth_client = frappe.get_doc("OAuth Client", self.client_id) + oauth_client.grant_type = "Implicit" + oauth_client.response_type = "Token" + oauth_client.save() + frappe.db.commit() + + # Go to Authorize url + self.driver.get( + "api/method/frappe.integrations.oauth2.authorize?client_id=" + + self.client_id + + "&scope=all%20openid&response_type=token&redirect_uri=http%3A%2F%2Flocalhost" + ) + + time.sleep(2) + + # Login + username = self.driver.find("#login_email")[0] + username.send_keys("test@example.com") + + password = self.driver.find("#login_password")[0] + password.send_keys("Eastern_43A1W") + + sign_in = self.driver.find(".btn-login")[0] + sign_in.submit() + + time.sleep(2) + + # Allow access to resource + allow = self.driver.find("#allow")[0] + allow.click() + + time.sleep(2) + + # Get token from redirected URL + response_url = dict(parse_qs(urlparse(self.driver.driver.current_url).fragment)) + + self.assertTrue(response_url.get("access_token")) + self.assertTrue(response_url.get("expires_in")) + self.assertTrue(response_url.get("scope")) + self.assertTrue(response_url.get("token_type")) diff --git a/frappe/twofactor.py b/frappe/twofactor.py index a8efc6a98b..9b402a8286 100644 --- a/frappe/twofactor.py +++ b/frappe/twofactor.py @@ -9,7 +9,7 @@ import pyotp, os from frappe.utils.background_jobs import enqueue from jinja2 import Template from pyqrcode import create as qrcreate -from six import StringIO +from six import BytesIO from base64 import b64encode, b32encode from frappe.utils import get_url, get_datetime, time_diff_in_seconds from six import iteritems, string_types @@ -317,11 +317,11 @@ def get_qr_svg_code(totp_uri): '''Get SVG code to display Qrcode for OTP.''' url = qrcreate(totp_uri) svg = '' - stream = StringIO() + stream = BytesIO() try: url.svg(stream, scale=4, background="#eee", module_color="#222") - svg = stream.getvalue().replace('\n', '') - svg = b64encode(bytes(svg)) + svg = stream.getvalue().decode().replace('\n', '') + svg = b64encode(svg.encode()) finally: stream.close() return svg diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py index 234ba3b929..c540b5bb52 100755 --- a/frappe/utils/background_jobs.py +++ b/frappe/utils/background_jobs.py @@ -18,7 +18,7 @@ queue_timeout = { } def enqueue(method, queue='default', timeout=300, event=None, - async=True, job_name=None, now=False, **kwargs): + async=True, job_name=None, now=False, enqueue_after_commit=False, **kwargs): ''' Enqueue method to be executed using a background worker @@ -37,17 +37,38 @@ def enqueue(method, queue='default', timeout=300, event=None, q = get_queue(queue, async=async) if not timeout: timeout = queue_timeout.get(queue) or 300 - - return q.enqueue_call(execute_job, timeout=timeout, - kwargs={ - "site": frappe.local.site, - "user": frappe.session.user, - "method": method, - "event": event, - "job_name": job_name or cstr(method), + queue_args = { + "site": frappe.local.site, + "user": frappe.session.user, + "method": method, + "event": event, + "job_name": job_name or cstr(method), + "async": async, + "kwargs": kwargs + } + if enqueue_after_commit: + if not frappe.flags.enqueue_after_commit: + frappe.flags.enqueue_after_commit = [] + + frappe.flags.enqueue_after_commit.append({ + "queue": queue, "async": async, - "kwargs": kwargs + "timeout": timeout, + "queue_args":queue_args }) + return frappe.flags.enqueue_after_commit + else: + return q.enqueue_call(execute_job, timeout=timeout, + kwargs=queue_args) + +def enqueue_doc(doctype, name=None, method=None, queue='default', timeout=300, + now=False, **kwargs): + '''Enqueue a method to be run on a document''' + enqueue('frappe.utils.background_jobs.run_doc_method', doctype=doctype, name=name, + doc_method=method, queue=queue, timeout=timeout, now=now, **kwargs) + +def run_doc_method(doctype, name, doc_method, **kwargs): + getattr(frappe.get_doc(doctype, name), doc_method)(**kwargs) def execute_job(site, method, event, job_name, kwargs, user=None, async=True, retry=0): '''Executes job in a worker, performs commit/rollback and logs if there is any error''' diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index 822b63e240..48fdc82179 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -191,7 +191,9 @@ def write_file(content, fname, is_private=0): # create directory (if not exists) frappe.create_folder(file_path) # write the file - with open(os.path.join(file_path.encode('utf-8'), fname.encode('utf-8')), 'w+') as f: + if isinstance(content, text_type): + content = content.encode() + with open(os.path.join(file_path.encode('utf-8'), fname.encode('utf-8')), 'wb+') as f: f.write(content) return get_files_path(fname, is_private=is_private) diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index f9a07e3212..84c3308e96 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe import re +import redis from frappe.utils import cint, strip_html_tags from frappe.model.base_document import get_controller from six import text_type @@ -232,6 +233,18 @@ def update_global_search(doc): frappe.flags.update_global_search.append( dict(doctype=doc.doctype, name=doc.name, content=' ||| '.join(content or ''), published=published, title=doc.get_title(), route=doc.get('route'))) + enqueue_global_search() + +def enqueue_global_search(): + if frappe.flags.update_global_search: + try: + frappe.enqueue('frappe.utils.global_search.sync_global_search', + now=frappe.flags.in_test or frappe.flags.in_install or frappe.flags.in_migrate, + flags=frappe.flags.update_global_search, enqueue_after_commit=True) + except redis.exceptions.ConnectionError: + sync_global_search() + + frappe.flags.update_global_search = [] def get_formatted_value(value, field): '''Prepare field from raw data''' diff --git a/frappe/utils/goal.py b/frappe/utils/goal.py index a126f161df..801648f249 100644 --- a/frappe/utils/goal.py +++ b/frappe/utils/goal.py @@ -97,7 +97,7 @@ def get_monthly_goal_graph_data(title, doctype, docname, goal_value_field, goal_ specific_values = [] summary_values = [ { - 'name': _("This month"), + 'title': _("This month"), 'color': 'green', 'value': formatted_value } @@ -106,19 +106,19 @@ def get_monthly_goal_graph_data(title, doctype, docname, goal_value_field, goal_ if float(goal) > 0: specific_values = [ { - 'name': _("Goal"), + 'title': _("Goal"), 'line_type': "dashed", 'value': goal }, ] summary_values += [ { - 'name': _("Goal"), + 'title': _("Goal"), 'color': 'blue', 'value': formatted_goal }, { - 'name': _("Completed"), + 'title': _("Completed"), 'color': 'green', 'value': str(int(round(float(current_month_value)/float(goal)*100))) + "%" } @@ -127,16 +127,16 @@ def get_monthly_goal_graph_data(title, doctype, docname, goal_value_field, goal_ data = { 'title': title, # 'subtitle': - 'y': [ - { - 'color': 'green', - 'values': values, - 'formatted': values_formatted - } - ], - 'x': { - 'values': months, - 'formatted': months_formatted + + 'data': { + 'datasets': [ + { + 'color': 'green', + 'values': values, + 'formatted': values_formatted + } + ], + 'labels': months }, 'specific_values': specific_values, diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index 6005346270..57c4051be6 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -84,6 +84,10 @@ def read_options_from_html(html): options = {} soup = BeautifulSoup(html, "html5lib") + options.update(prepare_header_footer(soup)) + + toggle_visible_pdf(soup) + # extract pdfkit options from html for html_id in ("margin-top", "margin-bottom", "margin-left", "margin-right", "page-size"): try: @@ -93,10 +97,6 @@ def read_options_from_html(html): except: pass - options.update(prepare_header_footer(soup)) - - toggle_visible_pdf(soup) - return soup.prettify(), options def prepare_header_footer(soup): diff --git a/frappe/utils/selenium_testdriver.py b/frappe/utils/selenium_testdriver.py index 2c6a224be7..3f3d82fe43 100644 --- a/frappe/utils/selenium_testdriver.py +++ b/frappe/utils/selenium_testdriver.py @@ -19,8 +19,8 @@ import frappe from ast import literal_eval class TestDriver(object): - def __init__(self, port='8000'): - self.port = port + def __init__(self, port=None): + self.port = port or frappe.get_site_config().webserver_port or '8000' chrome_options = Options() capabilities = DesiredCapabilities.CHROME @@ -84,16 +84,19 @@ class TestDriver(object): time.sleep(0.2) def set_field(self, fieldname, text): - elem = self.find(xpath='//input[@data-fieldname="{0}"]'.format(fieldname)) - elem[0].send_keys(text) + elem = self.wait_for(xpath='//input[@data-fieldname="{0}"]'.format(fieldname)) + time.sleep(0.2) + elem.send_keys(text) def set_select(self, fieldname, text): - elem = self.find(xpath='//select[@data-fieldname="{0}"]'.format(fieldname)) - elem[0].send_keys(text) + elem = self.wait_for(xpath='//select[@data-fieldname="{0}"]'.format(fieldname)) + time.sleep(0.2) + elem.send_keys(text) def set_text_editor(self, fieldname, text): - elem = self.find(xpath='//div[@data-fieldname="{0}"]//div[@contenteditable="true"]'.format(fieldname)) - elem[0].send_keys(text) + elem = self.wait_for(xpath='//div[@data-fieldname="{0}"]//div[@contenteditable="true"]'.format(fieldname)) + time.sleep(0.2) + elem.send_keys(text) def find(self, selector=None, everywhere=False, xpath=None): if xpath: @@ -164,7 +167,11 @@ class TestDriver(object): self.wait_for(xpath='//div[@data-page-route="{0}"]'.format('/'.join(args)), timeout=4) def click(self, css_selector, xpath=None): - self.wait_till_clickable(css_selector, xpath).click() + element = self.wait_till_clickable(css_selector, xpath) + self.scroll_to(css_selector) + time.sleep(0.5) + element.click() + return element def click_primary_action(self): selector = ".page-actions .primary-action" @@ -201,6 +208,7 @@ class TestDriver(object): return self.get_wait().until(EC.element_to_be_clickable( (by, selector))) + def execute_script(self, js): self.driver.execute_script(js) diff --git a/frappe/website/doctype/web_form/templates/web_form.html b/frappe/website/doctype/web_form/templates/web_form.html index a1cbbb0240..df0fa1de3e 100644 --- a/frappe/website/doctype/web_form/templates/web_form.html +++ b/frappe/website/doctype/web_form/templates/web_form.html @@ -98,9 +98,9 @@ {% if field.hidden %} - {% elif field.fieldtype == "HTML" and field.options %} -
        - {{ field.options }} + {% elif field.fieldtype == "HTML" %} +
        + {% if field.options %}{{ field.options }}{% endif %}
        {% elif field.fieldtype in ("Data", "Date", "Datetime", "Int", "Float") %}
        diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index 182d385c5f..43480df9b4 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -255,7 +255,7 @@ def get_context(context): js_path = os.path.join(os.path.dirname(self.web_form_module.__file__), scrub(self.name) + '.js') if os.path.exists(js_path): - context.script = open(js_path, 'r').read() + context.script = frappe.render_template(open(js_path, 'r').read(), context) css_path = os.path.join(os.path.dirname(self.web_form_module.__file__), scrub(self.name) + '.css') if os.path.exists(css_path): diff --git a/frappe/website/doctype/website_theme/website_theme.py b/frappe/website/doctype/website_theme/website_theme.py index 8f9841c924..04ea545c2a 100644 --- a/frappe/website/doctype/website_theme/website_theme.py +++ b/frappe/website/doctype/website_theme/website_theme.py @@ -43,7 +43,7 @@ class WebsiteTheme(Document): def export_doc(self): """Export to standard folder `[module]/website_theme/[name]/[name].json`.""" from frappe.modules.export_file import export_to_files - export_to_files(record_list=[['Website Theme', self.name]]) + export_to_files(record_list=[['Website Theme', self.name]], create_init=True) def clear_cache_if_current_theme(self): diff --git a/requirements.txt b/requirements.txt index 0216f85690..cf72c3109d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +boto3 chardet cssmin dropbox==7.3.1 @@ -48,4 +49,5 @@ pyotp pyqrcode pypng premailer +psycopg2