From 0a0af3a10ab6a15a4579fd3eaff9fc46014d958a Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Tue, 29 Nov 2022 14:05:34 +0100 Subject: [PATCH 01/26] basic workings are up. Just need to get the original timings in there --- scripts/generate_querygraph.py | 116 +- .../pythonpkg/duckdb_query_graph/__init__.py | 89 -- .../parse_profiling_output.js | 306 ---- .../duckdb_query_graph/query_graph.css | 136 -- tools/pythonpkg/duckdb_query_graph/raphael.js | 10 - tools/pythonpkg/duckdb_query_graph/treant.js | 1330 ----------------- 6 files changed, 111 insertions(+), 1876 deletions(-) delete mode 100644 tools/pythonpkg/duckdb_query_graph/__init__.py delete mode 100644 tools/pythonpkg/duckdb_query_graph/parse_profiling_output.js delete mode 100644 tools/pythonpkg/duckdb_query_graph/query_graph.css delete mode 100644 tools/pythonpkg/duckdb_query_graph/raphael.js delete mode 100644 tools/pythonpkg/duckdb_query_graph/treant.js diff --git a/scripts/generate_querygraph.py b/scripts/generate_querygraph.py index 5b685f9ae0ef..460bfd21f938 100644 --- a/scripts/generate_querygraph.py +++ b/scripts/generate_querygraph.py @@ -2,14 +2,12 @@ # This script takes a json file as input that is the result of the QueryProfiler of duckdb # and converts it into a Query Graph. -import os import sys -from python_helpers import open_utf8 +import json +import os sys.path.insert(0, 'benchmark') -import duckdb_query_graph - arguments = sys.argv if len(arguments) <= 1: print("Usage: python generate_querygraph.py [input.json] [output.html] [open={1,0}]") @@ -35,7 +33,115 @@ print("Incorrect input for open_output, expected TRUE or FALSE") exit(1) -duckdb_query_graph.generate(input, output) +def open_utf8(fpath, flags): + import sys + if sys.version_info[0] < 3: + return open(fpath, flags) + else: + return open(fpath, flags, encoding="utf8") + +def get_node_body(name, result, cardinality, timing, extra_info): + body = "" + body += "
" + body += f"

{name}

" + body += f"

{result}

" + body += f"

{timing}s

" + body += f"

cardinality = {cardinality}

" + body += f"

{extra_info}

" + body += "
" + body += "
" + return body + + +def generate_tree_recursive(json_graph): + node_prefix_html = "
  • " + node_suffix_html = "
  • " + node_body = get_node_body(json_graph["name"], + json_graph["timing"], + json_graph["cardinality"], + json_graph["extra_info"].replace("\n", "
    "), + json_graph["timings"]) + + children_html = "" + if len(json_graph['children']) >= 1: + children_html += "" + return node_prefix_html + node_body + children_html + node_suffix_html + + + +def generate_tree_html(graph_json): + json_graph = json.loads(graph_json) + tree_prefix = "
    \n
    " + # first level of json is general overview + # FIXME: make sure json output first level always has only 1 level + tree_body = generate_tree_recursive(json_graph['children'][0]) + return tree_prefix + tree_body + tree_suffix + + +def generate_style_html(graph_json, include_meta_info): + css = "\n" + return { + 'css': css, + 'libraries': '', + 'chart_script': '' + } + + +def generate_ipython(json_input): + from IPython.core.display import HTML + + html_output = generate_html(json_input, False) + + return HTML(""" + ${CSS} + ${LIBRARIES} +
    + ${CHART_SCRIPT} + """.replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', html_output['chart_script']).replace('${LIBRARIES}', html_output['libraries'])) + + +def generate(input_file, output_file): + with open_utf8(input_file, 'r') as f: + text = f.read() + + html_output = generate_style_html(text, True) + tree_output = generate_tree_html(text) + # print(html_output['chart_script']) + # finally create and write the html + with open_utf8(output_file, "w+") as f: + f.write(""" + + + + + Query Profile Graph for Query + ${CSS} + + + +
    +
    + + ${TREE} + + +""".replace("${CSS}", html_output['css']).replace('${TREE}', tree_output).replace('[INFOSEPARATOR]', '
    ')) + + +generate(input, output) with open(output, 'r') as f: text = f.read() diff --git a/tools/pythonpkg/duckdb_query_graph/__init__.py b/tools/pythonpkg/duckdb_query_graph/__init__.py deleted file mode 100644 index 23bc490f388d..000000000000 --- a/tools/pythonpkg/duckdb_query_graph/__init__.py +++ /dev/null @@ -1,89 +0,0 @@ -import json -import os - -qgraph_css = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'query_graph.css') -raphael_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'raphael.js') -treant_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'treant.js') -profile_output_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'parse_profiling_output.js') - -def open_utf8(fpath, flags): - import sys - if sys.version_info[0] < 3: - return open(fpath, flags) - else: - return open(fpath, flags, encoding="utf8") - - -def generate_html(graph_json, include_meta_info): - libraries = "" - - css = "" - - graph_json = graph_json.replace('\n', ' ').replace("'", "\\'").replace("\\n", "\\\\n") - - chart_script = """ - """.replace("${GRAPH_JSON}", graph_json).replace("${META_INFO}", "" if not include_meta_info else "document.getElementById('meta-info').innerHTML = meta_info;") - return { - 'css': css, - 'libraries': libraries, - 'chart_script': chart_script - } - -def generate_ipython(json_input): - from IPython.core.display import HTML - - html_output = generate_html(json_input, False) - - return HTML(""" - ${CSS} - ${LIBRARIES} -
    - ${CHART_SCRIPT} - """.replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', html_output['chart_script']).replace('${LIBRARIES}', html_output['libraries'])) - - -def generate(input_file, output_file): - with open_utf8(input_file, 'r') as f: - text = f.read() - - html_output = generate_html(text, True) - print(html_output['chart_script']) - # finally create and write the html - with open_utf8(output_file, "w+") as f: - f.write(""" - - - - - Query Profile Graph for Query - ${CSS} - - - ${LIBRARIES} - -
    -
    - - ${CHART_SCRIPT} - - -""".replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', html_output['chart_script']).replace('${LIBRARIES}', html_output['libraries'])) diff --git a/tools/pythonpkg/duckdb_query_graph/parse_profiling_output.js b/tools/pythonpkg/duckdb_query_graph/parse_profiling_output.js deleted file mode 100644 index a079fe2d007a..000000000000 --- a/tools/pythonpkg/duckdb_query_graph/parse_profiling_output.js +++ /dev/null @@ -1,306 +0,0 @@ - - -function gather_nodes(node, node_list, node_timings) { - var node_timing = node["timing"]; - var node_name = node["name"]; - var node_children = node["children"]; - if (node_timings[node_name] == undefined) { - node_timings[node_name] = 0 - } - node_timings[node_name] += node_timing; - node_list.push([node_timing, node]); - var total_time = 0; - for(var child in node_children) { - total_time += gather_nodes(node_children[child], node_list, node_timings); - } - return total_time + node_timing; -} - -function timing_to_str(timing) { - var timing_str = timing.toFixed(3); - if (timing_str == "0.000") { - return "0.0"; - } - return timing_str; -} - -function to_percentage(total_time, time) { - if (total_time == 0) { - return "?%"; - } - var fraction = 100 * (time / total_time); - if (fraction < 0.1) { - return "0%"; - } else { - return fraction.toFixed(1) + "%"; - } -} - -function add_spaces_to_big_number(number) { - if (number.length > 4) { - return add_spaces_to_big_number(number.substr(0, number.length - 3)) + " " + number.substr(number.length - 3, number.length) - } else { - return number; - } -} - -function toTitleCase(str) { - return str.replace( - /\w\S*/g, - function(txt) { - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); - } - ); -} - -function parse_profiling_output(graph_json) { - if (graph_json == null || graph_json == undefined) { - return ["No profile input.", null]; - } - var n = graph_json.indexOf('"result"'); - if (n < 0) { - // not a result we can do anything with - return [graph_json, null]; - } - // find the second occurrence of "result", if any - var second_n = graph_json.indexOf('"result"', n + 1) - if (second_n >= 0) { - // found one: find the last { before "result" - var split_index = graph_json.lastIndexOf("{", second_n); - graph_json = graph_json.substring(0, split_index); - } - // now parse the json - graph_data = JSON.parse(graph_json); - // tones for the different nodes, we use "earth tones" - tones = [ - "#493829", - "#816C5B", - "#A9A18C", - "#613318", - "#B99C6B", - "#A15038", - "#8F3B1B", - "#D57500", - "#DBCA69", - "#404F24", - "#668D3C", - "#BDD09F", - "#4E6172", - "#83929F", - "#A3ADB8" - ]; - // set fixed tones for the various common operators - fixed_tone_map = { - "ORDER_BY": 8, - "HASH_GROUP_BY": 7, - "FILTER": 13, - "SEQ_SCAN": 4, - "HASH_JOIN": 10, - "PROJECTION": 5, - "LIMIT": 0, - "PIECEWISE_MERGE_JOIN": 1, - "DISTINCT": 11, - "DELIM_SCAN": 9 - }; - // remaining (unknown) operators just fetch tones in order - var used_tones = {} - var remaining_tones = [] - for(var entry in fixed_tone_map) { - used_tones[fixed_tone_map[entry]] = true; - } - for(var tone in tones) { - if (used_tones[tone] !== undefined) { - remaining_tones.push(tone); - } - } - // first parse the total time - var total_time = graph_data["result"].toFixed(2); - - // assign an impact factor to nodes - // this is used for rendering opacity - var root_node = graph_data; - var node_list = [] - var node_timings = {} - var execution_time = gather_nodes(root_node, node_list, node_timings); - - // get the timing of the different query phases - var meta_info = ""; - var meta_timings = graph_data["timings"]; - if (meta_timings !== undefined) { - var keys = []; - var timings = {}; - for(var entry in meta_timings) { - timings[entry] = meta_timings[entry]; - keys.push(entry); - } - keys.sort(); - meta_info += ` - - - - -`; - // add the total time - meta_info += `` - // add the execution phase - var execution_time_percentage = to_percentage(total_time, execution_time); - meta_info += `` - execution_nodes = []; - for(var execution_node in node_timings) { - execution_nodes.push(execution_node); - } - execution_nodes.sort(function(a, b) { - var a_sort = node_timings[a]; - var b_sort = node_timings[b]; - if (a_sort < b_sort) { - return 1; - } else if (a_sort > b_sort) { - return -1; - } - return 0; - }); - for(var node_index in execution_nodes) { - var node = execution_nodes[node_index]; - var node_name = node; - var node_time = timing_to_str(node_timings[node]); - var node_percentage = to_percentage(total_time, node_timings[node]); - meta_info += `` - } - // add the planning/optimizer phase to the table - for(var key_index in keys) { - var key = keys[key_index]; - var is_subphase = key.includes(">"); - row_class = is_subphase ? 'subphase' : 'mainphase' - meta_info += `` - meta_info += '" - meta_info += '" - meta_info += "" - meta_info += "" - } - meta_info += '
    PhaseTimePercentage
    Total Time${total_time}s
    Execution${execution_time}s${execution_time_percentage}
    ${node_name}${node_time}s${node_percentage}
    ' - if (is_subphase) { - // subphase - var splits = key.split(" > "); - meta_info += "> " + toTitleCase(splits[splits.length - 1]).replace("_", " ") - } else { - // primary phase - meta_info += toTitleCase(key).replace("_", " ") - } - meta_info += "' + timing_to_str(timings[key]) + "s" + "" + to_percentage(total_time, timings[key]) + "
    ' - } - - // assign impacts based on % of execution spend in the node - // <0.1% = 0 - // <1% = 1 - // <5% = 2 - // anything else: 3 - for(var i = 0; i < node_list.length; i++) { - percentage = 100 * (node_list[i][0] / total_time) - if (percentage < 0.1) { - node_list[i][1]["impact"] = 0 - } else if (percentage < 1) { - node_list[i][1]["impact"] = 1 - } else if (percentage < 5) { - node_list[i][1]["impact"] = 2 - } else { - node_list[i][1]["impact"] = 3 - } - } - // now convert the JSON to the format used by treant.js - var parameters = { - "total_nodes": 0, - "current_tone": 0, - "max_nodes": 1000 - }; - - function convert_json(node, parameters) { - var node_name = node["name"]; - var node_timing = node["timing"]; - var node_extra_info = node["extra_info"] || ''; - var node_impact = node["impact"]; - var node_children = node["children"]; - parameters["total_nodes"] += 1 - // format is { text { name, title, contact }, HTMLclass, children: [...] } - var result = {} - var text_node = {} - title = "" - splits = node_extra_info.split('\n') - for(var i = 0; i < splits.length; i++) { - if (splits[i].length > 0) { - var text = splits[i].replace('"', ''); - if (text == "[INFOSEPARATOR]") { - text = "- - - - - -"; - } - title += text + "
    " - } - } - text_node["name"] = node_name + ' (' + node_timing + 's)'; - text_node["title"] = title; - text_node["contact"] = add_spaces_to_big_number(node["cardinality"]); - result["text"] = text_node; - if (fixed_tone_map[node_name] == undefined) { - fixed_tone_map[node_name] = remaining_tones[parameters["current_tone"]] - parameters["current_tone"] += 1 - if (parameters["current_tone"] >= remaining_tones.length) { - parameters["current_tone"] = 0 - } - } - if (node_impact == 0) { - impact_class = "small-impact" - } else if (node_impact == 1) { - impact_class = "medium-impact" - } else if (node_impact == 2) { - impact_class = "high-impact" - } else { - impact_class = "most-impact" - } - result["HTMLclass"] = 'impact_class ' + ' tone-' + fixed_tone_map[node_name]; - var result_children = [] - for(var i = 0; i < node_children.length; i++) { - result_children.push(convert_json(node_children[i], parameters)) - if (parameters["total_nodes"] > parameters["max_nodes"]) { - break; - } - } - result["children"] = result_children; - return result - } - - var new_json = convert_json(graph_data, parameters); - return [meta_info, new_json]; -} - -function create_graph(graph_data, container_id, container_class) { - var chart_config = { - chart: { - container: container_id, - nodeAlign: "BOTTOM", - connectors: { - type: 'step' - }, - node: { - HTMLclass: 'tree-node' - } - }, - nodeStructure: graph_data - }; - // create a dummy chart to figure out how wide/high it is - new Treant(chart_config ); - max_height = 0 - document.querySelectorAll('.tree-node').forEach(function(node) { - top_px = parseInt(node.style.top.substring(0, node.style.top.length - 2)) - if (top_px > max_height) { - max_height = top_px - } - }); - // now remove the chart we just created - document.querySelectorAll(container_id).forEach(function(node) { - node.innerHTML = "" - }); - // resize the chart - document.querySelectorAll(container_class).forEach(function(node) { - node.style.height = (max_height + 200) + "px" - }); - // and recreate it - new Treant(chart_config ); -}; diff --git a/tools/pythonpkg/duckdb_query_graph/query_graph.css b/tools/pythonpkg/duckdb_query_graph/query_graph.css deleted file mode 100644 index 99f4d83f63de..000000000000 --- a/tools/pythonpkg/duckdb_query_graph/query_graph.css +++ /dev/null @@ -1,136 +0,0 @@ - -.Treant { position: relative; overflow: hidden; padding: 0 !important; } -.Treant > .node, -.Treant > .pseudo { position: absolute; display: block; visibility: hidden; } -.Treant.loaded .node, -.Treant.loaded .pseudo { visibility: visible; } -.Treant > .pseudo { width: 0; height: 0; border: none; padding: 0; } -.Treant .collapse-switch { width: 3px; height: 3px; display: block; border: 1px solid black; position: absolute; top: 1px; right: 1px; cursor: pointer; } -.Treant .collapsed .collapse-switch { background-color: #868DEE; } -.Treant > .node img { border: none; float: left; } - -body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td { margin:0; padding:0; } -table { border-collapse:collapse; border-spacing:0; } -fieldset,img { border:0; } -address,caption,cite,code,dfn,em,strong,th,var { font-style:normal; font-weight:normal; } -caption,th { text-align:left; } -h1,h2,h3,h4,h5,h6 { font-size:100%; font-weight:normal; } -q:before,q:after { content:''; } -abbr,acronym { border:0; } - -body { background: #fff; } -/* optional Container STYLES */ -.chart { height: 500px; margin: 5px; width: 100%; } -.Treant > .node { } -.Treant > p { font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-weight: bold; font-size: 12px; } - -.tree-node { - padding: 2px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - background-color: #ffffff; - border: 1px solid #000; - width: 200px; - font-family: Tahoma; - font-size: 12px; -} - -.metainfo { - font-family: Tahoma; - font-weight: bold; -} - -.metainfo > th { - padding: 1px 5px; -} - -.mainphase { - background-color: #668D3C; -} -.subphase { - background-color: #B99C6B; -} -.phaseheader { - background-color: #83929F; -} - -.node-name { - font-weight: bold; - text-align: center; - font-size: 14px; -} - -.node-title { - text-align: center; -} - -.node-contact { - font-weight: bold; - text-align: center; -} -th { - background:none; -} -.small-impact { - opacity: 0.7; -} - -.medium-impact { - opacity: 0.8; -} - -.high-impact { - opacity: 0.9; -} - -.most-impact { - opacity: 1; -} - -.tone-1 { - background-color: #493829; -} -.tone-2 { - background-color: #816C5B; -} -.tone-3 { - background-color: #A9A18C; -} -.tone-4 { - background-color: #613318; -} -.tone-5 { - background-color: #B99C6B; -} -.tone-6 { - background-color: #A15038; -} -.tone-7 { - background-color: #8F3B1B; -} -.tone-8 { - background-color: #D57500; -} -.tone-9 { - background-color: #DBCA69; -} -.tone-10 { - background-color: #404F24; -} -.tone-11 { - background-color: #668D3C; -} -.tone-12 { - background-color: #BDD09F; -} -.tone-13 { - background-color: #4E6172; -} -.tone-14 { - background-color: #83929F; -} -.tone-15 { - background-color: #A3ADB8; -} - diff --git a/tools/pythonpkg/duckdb_query_graph/raphael.js b/tools/pythonpkg/duckdb_query_graph/raphael.js deleted file mode 100644 index 592a6f7561c1..000000000000 --- a/tools/pythonpkg/duckdb_query_graph/raphael.js +++ /dev/null @@ -1,10 +0,0 @@ -// ┌────────────────────────────────────────────────────────────────────┐ \\ -// │ Raphaël 2.1.0 - JavaScript Vector Library │ \\ -// ├────────────────────────────────────────────────────────────────────┤ \\ -// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com) │ \\ -// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com) │ \\ -// ├────────────────────────────────────────────────────────────────────┤ \\ -// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\ -// └────────────────────────────────────────────────────────────────────┘ \\ -(function(n){var e,t,r="0.4.2",f="hasOwnProperty",i=/[\.\/]/,o="*",u=function(){},l=function(n,e){return n-e},s={n:{}},p=function(n,r){n+="";var f,i=t,o=Array.prototype.slice.call(arguments,2),u=p.listeners(n),s=0,a=[],c={},h=[],d=e;e=n,t=0;for(var g=0,v=u.length;v>g;g++)"zIndex"in u[g]&&(a.push(u[g].zIndex),0>u[g].zIndex&&(c[u[g].zIndex]=u[g]));for(a.sort(l);0>a[s];)if(f=c[a[s++]],h.push(f.apply(r,o)),t)return t=i,h;for(g=0;v>g;g++)if(f=u[g],"zIndex"in f)if(f.zIndex==a[s]){if(h.push(f.apply(r,o)),t)break;do if(s++,f=c[a[s]],f&&h.push(f.apply(r,o)),t)break;while(f)}else c[f.zIndex]=f;else if(h.push(f.apply(r,o)),t)break;return t=i,e=d,h.length?h:null};p._events=s,p.listeners=function(n){var e,t,r,f,u,l,p,a,c=n.split(i),h=s,d=[h],g=[];for(f=0,u=c.length;u>f;f++){for(a=[],l=0,p=d.length;p>l;l++)for(h=d[l].n,t=[h[c[f]],h[o]],r=2;r--;)e=t[r],e&&(a.push(e),g=g.concat(e.f||[]));d=a}return g},p.on=function(n,e){if(n+="","function"!=typeof e)return function(){};for(var t=n.split(i),r=s,f=0,o=t.length;o>f;f++)r=r.n,r=r.hasOwnProperty(t[f])&&r[t[f]]||(r[t[f]]={n:{}});for(r.f=r.f||[],f=0,o=r.f.length;o>f;f++)if(r.f[f]==e)return u;return r.f.push(e),function(n){+n==+n&&(e.zIndex=+n)}},p.f=function(n){var e=[].slice.call(arguments,1);return function(){p.apply(null,[n,null].concat(e).concat([].slice.call(arguments,0)))}},p.stop=function(){t=1},p.nt=function(n){return n?RegExp("(?:\\.|\\/|^)"+n+"(?:\\.|\\/|$)").test(e):e},p.nts=function(){return e.split(i)},p.off=p.unbind=function(n,e){if(!n)return p._events=s={n:{}},void 0;var t,r,u,l,a,c,h,d=n.split(i),g=[s];for(l=0,a=d.length;a>l;l++)for(c=0;g.length>c;c+=u.length-2){if(u=[c,1],t=g[c].n,d[l]!=o)t[d[l]]&&u.push(t[d[l]]);else for(r in t)t[f](r)&&u.push(t[r]);g.splice.apply(g,u)}for(l=0,a=g.length;a>l;l++)for(t=g[l];t.n;){if(e){if(t.f){for(c=0,h=t.f.length;h>c;c++)if(t.f[c]==e){t.f.splice(c,1);break}!t.f.length&&delete t.f}for(r in t.n)if(t.n[f](r)&&t.n[r].f){var v=t.n[r].f;for(c=0,h=v.length;h>c;c++)if(v[c]==e){v.splice(c,1);break}!v.length&&delete t.n[r].f}}else{delete t.f;for(r in t.n)t.n[f](r)&&t.n[r].f&&delete t.n[r].f}t=t.n}},p.once=function(n,e){var t=function(){return p.unbind(n,t),e.apply(this,arguments)};return p.on(n,t)},p.version=r,p.toString=function(){return"You are running Eve "+r},"undefined"!=typeof module&&module.exports?module.exports=p:"undefined"!=typeof define?define("eve",[],function(){return p}):n.eve=p})(this);(function(t,e){"function"==typeof define&&define.amd?define("raphael",["eve"],e):t.Raphael=e(t.eve)})(this,function(t){function e(n){if(e.is(n,"function"))return y?n():t.on("raphael.DOMload",n);if(e.is(n,W))return e._engine.create[T](e,n.splice(0,3+e.is(n[0],G))).add(n);var r=Array.prototype.slice.call(arguments,0);if(e.is(r[r.length-1],"function")){var i=r.pop();return y?i.call(e._engine.create[T](e,r)):t.on("raphael.DOMload",function(){i.call(e._engine.create[T](e,r))})}return e._engine.create[T](e,arguments)}function n(t){if(Object(t)!==t)return t;var e=new t.constructor;for(var r in t)t[B](r)&&(e[r]=n(t[r]));return e}function r(t,e){for(var n=0,r=t.length;r>n;n++)if(t[n]===e)return t.push(t.splice(n,1)[0])}function i(t,e,n){function i(){var a=Array.prototype.slice.call(arguments,0),s=a.join("␀"),o=i.cache=i.cache||{},u=i.count=i.count||[];return o[B](s)?(r(u,s),n?n(o[s]):o[s]):(u.length>=1e3&&delete o[u.shift()],u.push(s),o[s]=t[T](e,a),n?n(o[s]):o[s])}return i}function a(){return this.hex}function s(t,e){for(var n=[],r=0,i=t.length;i-2*!e>r;r+=2){var a=[{x:+t[r-2],y:+t[r-1]},{x:+t[r],y:+t[r+1]},{x:+t[r+2],y:+t[r+3]},{x:+t[r+4],y:+t[r+5]}];e?r?i-4==r?a[3]={x:+t[0],y:+t[1]}:i-2==r&&(a[2]={x:+t[0],y:+t[1]},a[3]={x:+t[2],y:+t[3]}):a[0]={x:+t[i-2],y:+t[i-1]}:i-4==r?a[3]=a[2]:r||(a[0]={x:+t[r],y:+t[r+1]}),n.push(["C",(-a[0].x+6*a[1].x+a[2].x)/6,(-a[0].y+6*a[1].y+a[2].y)/6,(a[1].x+6*a[2].x-a[3].x)/6,(a[1].y+6*a[2].y-a[3].y)/6,a[2].x,a[2].y])}return n}function o(t,e,n,r,i){var a=-3*e+9*n-9*r+3*i,s=t*a+6*e-12*n+6*r;return t*s-3*e+3*n}function u(t,e,n,r,i,a,s,u,l){null==l&&(l=1),l=l>1?1:0>l?0:l;for(var h=l/2,c=12,f=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],p=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],d=0,g=0;c>g;g++){var x=h*f[g]+h,v=o(x,t,n,i,s),m=o(x,e,r,a,u),y=v*v+m*m;d+=p[g]*D.sqrt(y)}return h*d}function l(t,e,n,r,i,a,s,o,l){if(!(0>l||l>u(t,e,n,r,i,a,s,o))){var h,c=1,f=c/2,p=c-f,d=.01;for(h=u(t,e,n,r,i,a,s,o,p);V(h-l)>d;)f/=2,p+=(l>h?1:-1)*f,h=u(t,e,n,r,i,a,s,o,p);return p}}function h(t,e,n,r,i,a,s,o){if(!(z(t,n)z(i,s)||z(e,r)z(a,o))){var u=(t*r-e*n)*(i-s)-(t-n)*(i*o-a*s),l=(t*r-e*n)*(a-o)-(e-r)*(i*o-a*s),h=(t-n)*(a-o)-(e-r)*(i-s);if(h){var c=u/h,f=l/h,p=+c.toFixed(2),d=+f.toFixed(2);if(!(+O(t,n).toFixed(2)>p||p>+z(t,n).toFixed(2)||+O(i,s).toFixed(2)>p||p>+z(i,s).toFixed(2)||+O(e,r).toFixed(2)>d||d>+z(e,r).toFixed(2)||+O(a,o).toFixed(2)>d||d>+z(a,o).toFixed(2)))return{x:c,y:f}}}}function c(t,n,r){var i=e.bezierBBox(t),a=e.bezierBBox(n);if(!e.isBBoxIntersect(i,a))return r?0:[];for(var s=u.apply(0,t),o=u.apply(0,n),l=~~(s/5),c=~~(o/5),f=[],p=[],d={},g=r?0:[],x=0;l+1>x;x++){var v=e.findDotsAtSegment.apply(e,t.concat(x/l));f.push({x:v.x,y:v.y,t:x/l})}for(x=0;c+1>x;x++)v=e.findDotsAtSegment.apply(e,n.concat(x/c)),p.push({x:v.x,y:v.y,t:x/c});for(x=0;l>x;x++)for(var m=0;c>m;m++){var y=f[x],b=f[x+1],_=p[m],w=p[m+1],k=.001>V(b.x-y.x)?"y":"x",B=.001>V(w.x-_.x)?"y":"x",S=h(y.x,y.y,b.x,b.y,_.x,_.y,w.x,w.y);if(S){if(d[S.x.toFixed(4)]==S.y.toFixed(4))continue;d[S.x.toFixed(4)]=S.y.toFixed(4);var C=y.t+V((S[k]-y[k])/(b[k]-y[k]))*(b.t-y.t),F=_.t+V((S[B]-_[B])/(w[B]-_[B]))*(w.t-_.t);C>=0&&1>=C&&F>=0&&1>=F&&(r?g++:g.push({x:S.x,y:S.y,t1:C,t2:F}))}}return g}function f(t,n,r){t=e._path2curve(t),n=e._path2curve(n);for(var i,a,s,o,u,l,h,f,p,d,g=r?0:[],x=0,v=t.length;v>x;x++){var m=t[x];if("M"==m[0])i=u=m[1],a=l=m[2];else{"C"==m[0]?(p=[i,a].concat(m.slice(1)),i=p[6],a=p[7]):(p=[i,a,i,a,u,l,u,l],i=u,a=l);for(var y=0,b=n.length;b>y;y++){var _=n[y];if("M"==_[0])s=h=_[1],o=f=_[2];else{"C"==_[0]?(d=[s,o].concat(_.slice(1)),s=d[6],o=d[7]):(d=[s,o,s,o,h,f,h,f],s=h,o=f);var w=c(p,d,r);if(r)g+=w;else{for(var k=0,B=w.length;B>k;k++)w[k].segment1=x,w[k].segment2=y,w[k].bez1=p,w[k].bez2=d;g=g.concat(w)}}}}}return g}function p(t,e,n,r,i,a){null!=t?(this.a=+t,this.b=+e,this.c=+n,this.d=+r,this.e=+i,this.f=+a):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function d(){return this.x+E+this.y+E+this.width+" × "+this.height}function g(t,e,n,r,i,a){function s(t){return((c*t+h)*t+l)*t}function o(t,e){var n=u(t,e);return((d*n+p)*n+f)*n}function u(t,e){var n,r,i,a,o,u;for(i=t,u=0;8>u;u++){if(a=s(i)-t,e>V(a))return i;if(o=(3*c*i+2*h)*i+l,1e-6>V(o))break;i-=a/o}if(n=0,r=1,i=t,n>i)return n;if(i>r)return r;for(;r>n;){if(a=s(i),e>V(a-t))return i;t>a?n=i:r=i,i=(r-n)/2+n}return i}var l=3*e,h=3*(r-e)-l,c=1-l-h,f=3*n,p=3*(i-n)-f,d=1-f-p;return o(t,1/(200*a))}function x(t,e){var n=[],r={};if(this.ms=e,this.times=1,t){for(var i in t)t[B](i)&&(r[J(i)]=t[i],n.push(J(i)));n.sort(he)}this.anim=r,this.top=n[n.length-1],this.percents=n}function v(n,r,i,a,s,o){i=J(i);var u,l,h,c,f,d,x=n.ms,v={},m={},y={};if(a)for(w=0,k=on.length;k>w;w++){var b=on[w];if(b.el.id==r.id&&b.anim==n){b.percent!=i?(on.splice(w,1),h=1):l=b,r.attr(b.totalOrigin);break}}else a=+m;for(var w=0,k=n.percents.length;k>w;w++){if(n.percents[w]==i||n.percents[w]>a*n.top){i=n.percents[w],f=n.percents[w-1]||0,x=x/n.top*(i-f),c=n.percents[w+1],u=n.anim[i];break}a&&r.attr(n.anim[n.percents[w]])}if(u){if(l)l.initstatus=a,l.start=new Date-l.ms*a;else{for(var S in u)if(u[B](S)&&(ne[B](S)||r.paper.customAttributes[B](S)))switch(v[S]=r.attr(S),null==v[S]&&(v[S]=ee[S]),m[S]=u[S],ne[S]){case G:y[S]=(m[S]-v[S])/x;break;case"colour":v[S]=e.getRGB(v[S]);var C=e.getRGB(m[S]);y[S]={r:(C.r-v[S].r)/x,g:(C.g-v[S].g)/x,b:(C.b-v[S].b)/x};break;case"path":var F=Re(v[S],m[S]),T=F[1];for(v[S]=F[0],y[S]=[],w=0,k=v[S].length;k>w;w++){y[S][w]=[0];for(var A=1,P=v[S][w].length;P>A;A++)y[S][w][A]=(T[w][A]-v[S][w][A])/x}break;case"transform":var E=r._,R=Oe(E[S],m[S]);if(R)for(v[S]=R.from,m[S]=R.to,y[S]=[],y[S].real=!0,w=0,k=v[S].length;k>w;w++)for(y[S][w]=[v[S][w][0]],A=1,P=v[S][w].length;P>A;A++)y[S][w][A]=(m[S][w][A]-v[S][w][A])/x;else{var q=r.matrix||new p,j={_:{transform:E.transform},getBBox:function(){return r.getBBox(1)}};v[S]=[q.a,q.b,q.c,q.d,q.e,q.f],De(j,m[S]),m[S]=j._.transform,y[S]=[(j.matrix.a-q.a)/x,(j.matrix.b-q.b)/x,(j.matrix.c-q.c)/x,(j.matrix.d-q.d)/x,(j.matrix.e-q.e)/x,(j.matrix.f-q.f)/x]}break;case"csv":var D=M(u[S])[I](_),z=M(v[S])[I](_);if("clip-rect"==S)for(v[S]=z,y[S]=[],w=z.length;w--;)y[S][w]=(D[w]-v[S][w])/x;m[S]=D;break;default:for(D=[][L](u[S]),z=[][L](v[S]),y[S]=[],w=r.paper.customAttributes[S].length;w--;)y[S][w]=((D[w]||0)-(z[w]||0))/x}var O=u.easing,V=e.easing_formulas[O];if(!V)if(V=M(O).match(Z),V&&5==V.length){var X=V;V=function(t){return g(t,+X[1],+X[2],+X[3],+X[4],x)}}else V=fe;if(d=u.start||n.start||+new Date,b={anim:n,percent:i,timestamp:d,start:d+(n.del||0),status:0,initstatus:a||0,stop:!1,ms:x,easing:V,from:v,diff:y,to:m,el:r,callback:u.callback,prev:f,next:c,repeat:o||n.times,origin:r.attr(),totalOrigin:s},on.push(b),a&&!l&&!h&&(b.stop=!0,b.start=new Date-x*a,1==on.length))return ln();h&&(b.start=new Date-b.ms*a),1==on.length&&un(ln)}t("raphael.anim.start."+r.id,r,n)}}function m(t){for(var e=0;on.length>e;e++)on[e].el.paper==t&&on.splice(e--,1)}e.version="2.1.0",e.eve=t;var y,b,_=/[, ]+/,w={circle:1,rect:1,path:1,ellipse:1,text:1,image:1},k=/\{(\d+)\}/g,B="hasOwnProperty",S={doc:document,win:window},C={was:Object.prototype[B].call(S.win,"Raphael"),is:S.win.Raphael},F=function(){this.ca=this.customAttributes={}},T="apply",L="concat",A="createTouch"in S.doc,P="",E=" ",M=String,I="split",R="click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[I](E),q={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},j=M.prototype.toLowerCase,D=Math,z=D.max,O=D.min,V=D.abs,X=D.pow,Y=D.PI,G="number",N="string",W="array",$=Object.prototype.toString,H=(e._ISURL=/^url\(['"]?([^\)]+?)['"]?\)$/i,/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i),U={NaN:1,Infinity:1,"-Infinity":1},Z=/^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,Q=D.round,J=parseFloat,K=parseInt,te=M.prototype.toUpperCase,ee=e._availableAttrs={"arrow-end":"none","arrow-start":"none",blur:0,"clip-rect":"0 0 1e9 1e9",cursor:"default",cx:0,cy:0,fill:"#fff","fill-opacity":1,font:'10px "Arial"',"font-family":'"Arial"',"font-size":"10","font-style":"normal","font-weight":400,gradient:0,height:0,href:"http://raphaeljs.com/","letter-spacing":0,opacity:1,path:"M0,0",r:0,rx:0,ry:0,src:"",stroke:"#000","stroke-dasharray":"","stroke-linecap":"butt","stroke-linejoin":"butt","stroke-miterlimit":0,"stroke-opacity":1,"stroke-width":1,target:"_blank","text-anchor":"middle",title:"Raphael",transform:"",width:0,x:0,y:0},ne=e._availableAnimAttrs={blur:G,"clip-rect":"csv",cx:G,cy:G,fill:"colour","fill-opacity":G,"font-size":G,height:G,opacity:G,path:"path",r:G,rx:G,ry:G,stroke:"colour","stroke-opacity":G,"stroke-width":G,transform:"transform",width:G,x:G,y:G},re=/[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,ie={hs:1,rg:1},ae=/,?([achlmqrstvxz]),?/gi,se=/([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/gi,oe=/([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/gi,ue=/(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/gi,le=(e._radial_gradient=/^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,{}),he=function(t,e){return J(t)-J(e)},ce=function(){},fe=function(t){return t},pe=e._rectPath=function(t,e,n,r,i){return i?[["M",t+i,e],["l",n-2*i,0],["a",i,i,0,0,1,i,i],["l",0,r-2*i],["a",i,i,0,0,1,-i,i],["l",2*i-n,0],["a",i,i,0,0,1,-i,-i],["l",0,2*i-r],["a",i,i,0,0,1,i,-i],["z"]]:[["M",t,e],["l",n,0],["l",0,r],["l",-n,0],["z"]]},de=function(t,e,n,r){return null==r&&(r=n),[["M",t,e],["m",0,-r],["a",n,r,0,1,1,0,2*r],["a",n,r,0,1,1,0,-2*r],["z"]]},ge=e._getPath={path:function(t){return t.attr("path")},circle:function(t){var e=t.attrs;return de(e.cx,e.cy,e.r)},ellipse:function(t){var e=t.attrs;return de(e.cx,e.cy,e.rx,e.ry)},rect:function(t){var e=t.attrs;return pe(e.x,e.y,e.width,e.height,e.r)},image:function(t){var e=t.attrs;return pe(e.x,e.y,e.width,e.height)},text:function(t){var e=t._getBBox();return pe(e.x,e.y,e.width,e.height)},set:function(t){var e=t._getBBox();return pe(e.x,e.y,e.width,e.height)}},xe=e.mapPath=function(t,e){if(!e)return t;var n,r,i,a,s,o,u;for(t=Re(t),i=0,s=t.length;s>i;i++)for(u=t[i],a=1,o=u.length;o>a;a+=2)n=e.x(u[a],u[a+1]),r=e.y(u[a],u[a+1]),u[a]=n,u[a+1]=r;return t};if(e._g=S,e.type=S.win.SVGAngle||S.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML","VML"==e.type){var ve,me=S.doc.createElement("div");if(me.innerHTML='',ve=me.firstChild,ve.style.behavior="url(#default#VML)",!ve||"object"!=typeof ve.adj)return e.type=P;me=null}e.svg=!(e.vml="VML"==e.type),e._Paper=F,e.fn=b=F.prototype=e.prototype,e._id=0,e._oid=0,e.is=function(t,e){return e=j.call(e),"finite"==e?!U[B](+t):"array"==e?t instanceof Array:"null"==e&&null===t||e==typeof t&&null!==t||"object"==e&&t===Object(t)||"array"==e&&Array.isArray&&Array.isArray(t)||$.call(t).slice(8,-1).toLowerCase()==e},e.angle=function(t,n,r,i,a,s){if(null==a){var o=t-r,u=n-i;return o||u?(180+180*D.atan2(-u,-o)/Y+360)%360:0}return e.angle(t,n,a,s)-e.angle(r,i,a,s)},e.rad=function(t){return t%360*Y/180},e.deg=function(t){return 180*t/Y%360},e.snapTo=function(t,n,r){if(r=e.is(r,"finite")?r:10,e.is(t,W)){for(var i=t.length;i--;)if(r>=V(t[i]-n))return t[i]}else{t=+t;var a=n%t;if(r>a)return n-a;if(a>t-r)return n-a+t}return n},e.createUUID=function(t,e){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(t,e).toUpperCase()}}(/[xy]/g,function(t){var e=0|16*D.random(),n="x"==t?e:8|3&e;return n.toString(16)}),e.setWindow=function(n){t("raphael.setWindow",e,S.win,n),S.win=n,S.doc=S.win.document,e._engine.initWin&&e._engine.initWin(S.win)};var ye=function(t){if(e.vml){var n,r=/^\s+|\s+$/g;try{var a=new ActiveXObject("htmlfile");a.write(""),a.close(),n=a.body}catch(s){n=createPopup().document.body}var o=n.createTextRange();ye=i(function(t){try{n.style.color=M(t).replace(r,P);var e=o.queryCommandValue("ForeColor");return e=(255&e)<<16|65280&e|(16711680&e)>>>16,"#"+("000000"+e.toString(16)).slice(-6)}catch(i){return"none"}})}else{var u=S.doc.createElement("i");u.title="Raphaël Colour Picker",u.style.display="none",S.doc.body.appendChild(u),ye=i(function(t){return u.style.color=t,S.doc.defaultView.getComputedStyle(u,P).getPropertyValue("color")})}return ye(t)},be=function(){return"hsb("+[this.h,this.s,this.b]+")"},_e=function(){return"hsl("+[this.h,this.s,this.l]+")"},we=function(){return this.hex},ke=function(t,n,r){if(null==n&&e.is(t,"object")&&"r"in t&&"g"in t&&"b"in t&&(r=t.b,n=t.g,t=t.r),null==n&&e.is(t,N)){var i=e.getRGB(t);t=i.r,n=i.g,r=i.b}return(t>1||n>1||r>1)&&(t/=255,n/=255,r/=255),[t,n,r]},Be=function(t,n,r,i){t*=255,n*=255,r*=255;var a={r:t,g:n,b:r,hex:e.rgb(t,n,r),toString:we};return e.is(i,"finite")&&(a.opacity=i),a};e.color=function(t){var n;return e.is(t,"object")&&"h"in t&&"s"in t&&"b"in t?(n=e.hsb2rgb(t),t.r=n.r,t.g=n.g,t.b=n.b,t.hex=n.hex):e.is(t,"object")&&"h"in t&&"s"in t&&"l"in t?(n=e.hsl2rgb(t),t.r=n.r,t.g=n.g,t.b=n.b,t.hex=n.hex):(e.is(t,"string")&&(t=e.getRGB(t)),e.is(t,"object")&&"r"in t&&"g"in t&&"b"in t?(n=e.rgb2hsl(t),t.h=n.h,t.s=n.s,t.l=n.l,n=e.rgb2hsb(t),t.v=n.b):(t={hex:"none"},t.r=t.g=t.b=t.h=t.s=t.v=t.l=-1)),t.toString=we,t},e.hsb2rgb=function(t,e,n,r){this.is(t,"object")&&"h"in t&&"s"in t&&"b"in t&&(n=t.b,e=t.s,t=t.h,r=t.o),t*=360;var i,a,s,o,u;return t=t%360/60,u=n*e,o=u*(1-V(t%2-1)),i=a=s=n-u,t=~~t,i+=[u,o,0,0,o,u][t],a+=[o,u,u,o,0,0][t],s+=[0,0,o,u,u,o][t],Be(i,a,s,r)},e.hsl2rgb=function(t,e,n,r){this.is(t,"object")&&"h"in t&&"s"in t&&"l"in t&&(n=t.l,e=t.s,t=t.h),(t>1||e>1||n>1)&&(t/=360,e/=100,n/=100),t*=360;var i,a,s,o,u;return t=t%360/60,u=2*e*(.5>n?n:1-n),o=u*(1-V(t%2-1)),i=a=s=n-u/2,t=~~t,i+=[u,o,0,0,o,u][t],a+=[o,u,u,o,0,0][t],s+=[0,0,o,u,u,o][t],Be(i,a,s,r)},e.rgb2hsb=function(t,e,n){n=ke(t,e,n),t=n[0],e=n[1],n=n[2];var r,i,a,s;return a=z(t,e,n),s=a-O(t,e,n),r=0==s?null:a==t?(e-n)/s:a==e?(n-t)/s+2:(t-e)/s+4,r=60*((r+360)%6)/360,i=0==s?0:s/a,{h:r,s:i,b:a,toString:be}},e.rgb2hsl=function(t,e,n){n=ke(t,e,n),t=n[0],e=n[1],n=n[2];var r,i,a,s,o,u;return s=z(t,e,n),o=O(t,e,n),u=s-o,r=0==u?null:s==t?(e-n)/u:s==e?(n-t)/u+2:(t-e)/u+4,r=60*((r+360)%6)/360,a=(s+o)/2,i=0==u?0:.5>a?u/(2*a):u/(2-2*a),{h:r,s:i,l:a,toString:_e}},e._path2string=function(){return this.join(",").replace(ae,"$1")},e._preload=function(t,e){var n=S.doc.createElement("img");n.style.cssText="position:absolute;left:-9999em;top:-9999em",n.onload=function(){e.call(this),this.onload=null,S.doc.body.removeChild(this)},n.onerror=function(){S.doc.body.removeChild(this)},S.doc.body.appendChild(n),n.src=t},e.getRGB=i(function(t){if(!t||(t=M(t)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:a};if("none"==t)return{r:-1,g:-1,b:-1,hex:"none",toString:a};!(ie[B](t.toLowerCase().substring(0,2))||"#"==t.charAt())&&(t=ye(t));var n,r,i,s,o,u,l=t.match(H);return l?(l[2]&&(i=K(l[2].substring(5),16),r=K(l[2].substring(3,5),16),n=K(l[2].substring(1,3),16)),l[3]&&(i=K((o=l[3].charAt(3))+o,16),r=K((o=l[3].charAt(2))+o,16),n=K((o=l[3].charAt(1))+o,16)),l[4]&&(u=l[4][I](re),n=J(u[0]),"%"==u[0].slice(-1)&&(n*=2.55),r=J(u[1]),"%"==u[1].slice(-1)&&(r*=2.55),i=J(u[2]),"%"==u[2].slice(-1)&&(i*=2.55),"rgba"==l[1].toLowerCase().slice(0,4)&&(s=J(u[3])),u[3]&&"%"==u[3].slice(-1)&&(s/=100)),l[5]?(u=l[5][I](re),n=J(u[0]),"%"==u[0].slice(-1)&&(n*=2.55),r=J(u[1]),"%"==u[1].slice(-1)&&(r*=2.55),i=J(u[2]),"%"==u[2].slice(-1)&&(i*=2.55),("deg"==u[0].slice(-3)||"°"==u[0].slice(-1))&&(n/=360),"hsba"==l[1].toLowerCase().slice(0,4)&&(s=J(u[3])),u[3]&&"%"==u[3].slice(-1)&&(s/=100),e.hsb2rgb(n,r,i,s)):l[6]?(u=l[6][I](re),n=J(u[0]),"%"==u[0].slice(-1)&&(n*=2.55),r=J(u[1]),"%"==u[1].slice(-1)&&(r*=2.55),i=J(u[2]),"%"==u[2].slice(-1)&&(i*=2.55),("deg"==u[0].slice(-3)||"°"==u[0].slice(-1))&&(n/=360),"hsla"==l[1].toLowerCase().slice(0,4)&&(s=J(u[3])),u[3]&&"%"==u[3].slice(-1)&&(s/=100),e.hsl2rgb(n,r,i,s)):(l={r:n,g:r,b:i,toString:a},l.hex="#"+(16777216|i|r<<8|n<<16).toString(16).slice(1),e.is(s,"finite")&&(l.opacity=s),l)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:a}},e),e.hsb=i(function(t,n,r){return e.hsb2rgb(t,n,r).hex}),e.hsl=i(function(t,n,r){return e.hsl2rgb(t,n,r).hex}),e.rgb=i(function(t,e,n){return"#"+(16777216|n|e<<8|t<<16).toString(16).slice(1)}),e.getColor=function(t){var e=this.getColor.start=this.getColor.start||{h:0,s:1,b:t||.75},n=this.hsb2rgb(e.h,e.s,e.b);return e.h+=.075,e.h>1&&(e.h=0,e.s-=.2,0>=e.s&&(this.getColor.start={h:0,s:1,b:e.b})),n.hex},e.getColor.reset=function(){delete this.start},e.parsePathString=function(t){if(!t)return null;var n=Se(t);if(n.arr)return Fe(n.arr);var r={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},i=[];return e.is(t,W)&&e.is(t[0],W)&&(i=Fe(t)),i.length||M(t).replace(se,function(t,e,n){var a=[],s=e.toLowerCase();if(n.replace(ue,function(t,e){e&&a.push(+e)}),"m"==s&&a.length>2&&(i.push([e][L](a.splice(0,2))),s="l",e="m"==e?"l":"L"),"r"==s)i.push([e][L](a));else for(;a.length>=r[s]&&(i.push([e][L](a.splice(0,r[s]))),r[s]););}),i.toString=e._path2string,n.arr=Fe(i),i},e.parseTransformString=i(function(t){if(!t)return null;var n=[];return e.is(t,W)&&e.is(t[0],W)&&(n=Fe(t)),n.length||M(t).replace(oe,function(t,e,r){var i=[];j.call(e),r.replace(ue,function(t,e){e&&i.push(+e)}),n.push([e][L](i))}),n.toString=e._path2string,n});var Se=function(t){var e=Se.ps=Se.ps||{};return e[t]?e[t].sleep=100:e[t]={sleep:100},setTimeout(function(){for(var n in e)e[B](n)&&n!=t&&(e[n].sleep--,!e[n].sleep&&delete e[n])}),e[t]};e.findDotsAtSegment=function(t,e,n,r,i,a,s,o,u){var l=1-u,h=X(l,3),c=X(l,2),f=u*u,p=f*u,d=h*t+3*c*u*n+3*l*u*u*i+p*s,g=h*e+3*c*u*r+3*l*u*u*a+p*o,x=t+2*u*(n-t)+f*(i-2*n+t),v=e+2*u*(r-e)+f*(a-2*r+e),m=n+2*u*(i-n)+f*(s-2*i+n),y=r+2*u*(a-r)+f*(o-2*a+r),b=l*t+u*n,_=l*e+u*r,w=l*i+u*s,k=l*a+u*o,B=90-180*D.atan2(x-m,v-y)/Y;return(x>m||y>v)&&(B+=180),{x:d,y:g,m:{x:x,y:v},n:{x:m,y:y},start:{x:b,y:_},end:{x:w,y:k},alpha:B}},e.bezierBBox=function(t,n,r,i,a,s,o,u){e.is(t,"array")||(t=[t,n,r,i,a,s,o,u]);var l=Ie.apply(null,t);return{x:l.min.x,y:l.min.y,x2:l.max.x,y2:l.max.y,width:l.max.x-l.min.x,height:l.max.y-l.min.y}},e.isPointInsideBBox=function(t,e,n){return e>=t.x&&t.x2>=e&&n>=t.y&&t.y2>=n},e.isBBoxIntersect=function(t,n){var r=e.isPointInsideBBox;return r(n,t.x,t.y)||r(n,t.x2,t.y)||r(n,t.x,t.y2)||r(n,t.x2,t.y2)||r(t,n.x,n.y)||r(t,n.x2,n.y)||r(t,n.x,n.y2)||r(t,n.x2,n.y2)||(t.xn.x||n.xt.x)&&(t.yn.y||n.yt.y)},e.pathIntersection=function(t,e){return f(t,e)},e.pathIntersectionNumber=function(t,e){return f(t,e,1)},e.isPointInsidePath=function(t,n,r){var i=e.pathBBox(t);return e.isPointInsideBBox(i,n,r)&&1==f(t,[["M",n,r],["H",i.x2+10]],1)%2},e._removedFactory=function(e){return function(){t("raphael.log",null,"Raphaël: you are calling to method “"+e+"” of removed object",e)}};var Ce=e.pathBBox=function(t){var e=Se(t);if(e.bbox)return n(e.bbox);if(!t)return{x:0,y:0,width:0,height:0,x2:0,y2:0};t=Re(t);for(var r,i=0,a=0,s=[],o=[],u=0,l=t.length;l>u;u++)if(r=t[u],"M"==r[0])i=r[1],a=r[2],s.push(i),o.push(a);else{var h=Ie(i,a,r[1],r[2],r[3],r[4],r[5],r[6]);s=s[L](h.min.x,h.max.x),o=o[L](h.min.y,h.max.y),i=r[5],a=r[6]}var c=O[T](0,s),f=O[T](0,o),p=z[T](0,s),d=z[T](0,o),g=p-c,x=d-f,v={x:c,y:f,x2:p,y2:d,width:g,height:x,cx:c+g/2,cy:f+x/2};return e.bbox=n(v),v},Fe=function(t){var r=n(t);return r.toString=e._path2string,r},Te=e._pathToRelative=function(t){var n=Se(t);if(n.rel)return Fe(n.rel);e.is(t,W)&&e.is(t&&t[0],W)||(t=e.parsePathString(t));var r=[],i=0,a=0,s=0,o=0,u=0;"M"==t[0][0]&&(i=t[0][1],a=t[0][2],s=i,o=a,u++,r.push(["M",i,a]));for(var l=u,h=t.length;h>l;l++){var c=r[l]=[],f=t[l];if(f[0]!=j.call(f[0]))switch(c[0]=j.call(f[0]),c[0]){case"a":c[1]=f[1],c[2]=f[2],c[3]=f[3],c[4]=f[4],c[5]=f[5],c[6]=+(f[6]-i).toFixed(3),c[7]=+(f[7]-a).toFixed(3);break;case"v":c[1]=+(f[1]-a).toFixed(3);break;case"m":s=f[1],o=f[2];default:for(var p=1,d=f.length;d>p;p++)c[p]=+(f[p]-(p%2?i:a)).toFixed(3)}else{c=r[l]=[],"m"==f[0]&&(s=f[1]+i,o=f[2]+a);for(var g=0,x=f.length;x>g;g++)r[l][g]=f[g]}var v=r[l].length;switch(r[l][0]){case"z":i=s,a=o;break;case"h":i+=+r[l][v-1];break;case"v":a+=+r[l][v-1];break;default:i+=+r[l][v-2],a+=+r[l][v-1]}}return r.toString=e._path2string,n.rel=Fe(r),r},Le=e._pathToAbsolute=function(t){var n=Se(t);if(n.abs)return Fe(n.abs);if(e.is(t,W)&&e.is(t&&t[0],W)||(t=e.parsePathString(t)),!t||!t.length)return[["M",0,0]];var r=[],i=0,a=0,o=0,u=0,l=0;"M"==t[0][0]&&(i=+t[0][1],a=+t[0][2],o=i,u=a,l++,r[0]=["M",i,a]);for(var h,c,f=3==t.length&&"M"==t[0][0]&&"R"==t[1][0].toUpperCase()&&"Z"==t[2][0].toUpperCase(),p=l,d=t.length;d>p;p++){if(r.push(h=[]),c=t[p],c[0]!=te.call(c[0]))switch(h[0]=te.call(c[0]),h[0]){case"A":h[1]=c[1],h[2]=c[2],h[3]=c[3],h[4]=c[4],h[5]=c[5],h[6]=+(c[6]+i),h[7]=+(c[7]+a);break;case"V":h[1]=+c[1]+a;break;case"H":h[1]=+c[1]+i;break;case"R":for(var g=[i,a][L](c.slice(1)),x=2,v=g.length;v>x;x++)g[x]=+g[x]+i,g[++x]=+g[x]+a;r.pop(),r=r[L](s(g,f));break;case"M":o=+c[1]+i,u=+c[2]+a;default:for(x=1,v=c.length;v>x;x++)h[x]=+c[x]+(x%2?i:a)}else if("R"==c[0])g=[i,a][L](c.slice(1)),r.pop(),r=r[L](s(g,f)),h=["R"][L](c.slice(-2));else for(var m=0,y=c.length;y>m;m++)h[m]=c[m];switch(h[0]){case"Z":i=o,a=u;break;case"H":i=h[1];break;case"V":a=h[1];break;case"M":o=h[h.length-2],u=h[h.length-1];default:i=h[h.length-2],a=h[h.length-1]}}return r.toString=e._path2string,n.abs=Fe(r),r},Ae=function(t,e,n,r){return[t,e,n,r,n,r]},Pe=function(t,e,n,r,i,a){var s=1/3,o=2/3;return[s*t+o*n,s*e+o*r,s*i+o*n,s*a+o*r,i,a]},Ee=function(t,e,n,r,a,s,o,u,l,h){var c,f=120*Y/180,p=Y/180*(+a||0),d=[],g=i(function(t,e,n){var r=t*D.cos(n)-e*D.sin(n),i=t*D.sin(n)+e*D.cos(n);return{x:r,y:i}});if(h)B=h[0],S=h[1],w=h[2],k=h[3];else{c=g(t,e,-p),t=c.x,e=c.y,c=g(u,l,-p),u=c.x,l=c.y;var x=(D.cos(Y/180*a),D.sin(Y/180*a),(t-u)/2),v=(e-l)/2,m=x*x/(n*n)+v*v/(r*r);m>1&&(m=D.sqrt(m),n=m*n,r=m*r);var y=n*n,b=r*r,_=(s==o?-1:1)*D.sqrt(V((y*b-y*v*v-b*x*x)/(y*v*v+b*x*x))),w=_*n*v/r+(t+u)/2,k=_*-r*x/n+(e+l)/2,B=D.asin(((e-k)/r).toFixed(9)),S=D.asin(((l-k)/r).toFixed(9));B=w>t?Y-B:B,S=w>u?Y-S:S,0>B&&(B=2*Y+B),0>S&&(S=2*Y+S),o&&B>S&&(B-=2*Y),!o&&S>B&&(S-=2*Y)}var C=S-B;if(V(C)>f){var F=S,T=u,A=l;S=B+f*(o&&S>B?1:-1),u=w+n*D.cos(S),l=k+r*D.sin(S),d=Ee(u,l,n,r,a,0,o,T,A,[S,F,w,k])}C=S-B;var P=D.cos(B),E=D.sin(B),M=D.cos(S),R=D.sin(S),q=D.tan(C/4),j=4/3*n*q,z=4/3*r*q,O=[t,e],X=[t+j*E,e-z*P],G=[u+j*R,l-z*M],N=[u,l];if(X[0]=2*O[0]-X[0],X[1]=2*O[1]-X[1],h)return[X,G,N][L](d);d=[X,G,N][L](d).join()[I](",");for(var W=[],$=0,H=d.length;H>$;$++)W[$]=$%2?g(d[$-1],d[$],p).y:g(d[$],d[$+1],p).x;return W},Me=function(t,e,n,r,i,a,s,o,u){var l=1-u;return{x:X(l,3)*t+3*X(l,2)*u*n+3*l*u*u*i+X(u,3)*s,y:X(l,3)*e+3*X(l,2)*u*r+3*l*u*u*a+X(u,3)*o}},Ie=i(function(t,e,n,r,i,a,s,o){var u,l=i-2*n+t-(s-2*i+n),h=2*(n-t)-2*(i-n),c=t-n,f=(-h+D.sqrt(h*h-4*l*c))/2/l,p=(-h-D.sqrt(h*h-4*l*c))/2/l,d=[e,o],g=[t,s];return V(f)>"1e12"&&(f=.5),V(p)>"1e12"&&(p=.5),f>0&&1>f&&(u=Me(t,e,n,r,i,a,s,o,f),g.push(u.x),d.push(u.y)),p>0&&1>p&&(u=Me(t,e,n,r,i,a,s,o,p),g.push(u.x),d.push(u.y)),l=a-2*r+e-(o-2*a+r),h=2*(r-e)-2*(a-r),c=e-r,f=(-h+D.sqrt(h*h-4*l*c))/2/l,p=(-h-D.sqrt(h*h-4*l*c))/2/l,V(f)>"1e12"&&(f=.5),V(p)>"1e12"&&(p=.5),f>0&&1>f&&(u=Me(t,e,n,r,i,a,s,o,f),g.push(u.x),d.push(u.y)),p>0&&1>p&&(u=Me(t,e,n,r,i,a,s,o,p),g.push(u.x),d.push(u.y)),{min:{x:O[T](0,g),y:O[T](0,d)},max:{x:z[T](0,g),y:z[T](0,d)}}}),Re=e._path2curve=i(function(t,e){var n=!e&&Se(t);if(!e&&n.curve)return Fe(n.curve);for(var r=Le(t),i=e&&Le(e),a={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},s={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},o=(function(t,e){var n,r;if(!t)return["C",e.x,e.y,e.x,e.y,e.x,e.y];switch(!(t[0]in{T:1,Q:1})&&(e.qx=e.qy=null),t[0]){case"M":e.X=t[1],e.Y=t[2];break;case"A":t=["C"][L](Ee[T](0,[e.x,e.y][L](t.slice(1))));break;case"S":n=e.x+(e.x-(e.bx||e.x)),r=e.y+(e.y-(e.by||e.y)),t=["C",n,r][L](t.slice(1));break;case"T":e.qx=e.x+(e.x-(e.qx||e.x)),e.qy=e.y+(e.y-(e.qy||e.y)),t=["C"][L](Pe(e.x,e.y,e.qx,e.qy,t[1],t[2]));break;case"Q":e.qx=t[1],e.qy=t[2],t=["C"][L](Pe(e.x,e.y,t[1],t[2],t[3],t[4]));break;case"L":t=["C"][L](Ae(e.x,e.y,t[1],t[2]));break;case"H":t=["C"][L](Ae(e.x,e.y,t[1],e.y));break;case"V":t=["C"][L](Ae(e.x,e.y,e.x,t[1]));break;case"Z":t=["C"][L](Ae(e.x,e.y,e.X,e.Y))}return t}),u=function(t,e){if(t[e].length>7){t[e].shift();for(var n=t[e];n.length;)t.splice(e++,0,["C"][L](n.splice(0,6)));t.splice(e,1),c=z(r.length,i&&i.length||0)}},l=function(t,e,n,a,s){t&&e&&"M"==t[s][0]&&"M"!=e[s][0]&&(e.splice(s,0,["M",a.x,a.y]),n.bx=0,n.by=0,n.x=t[s][1],n.y=t[s][2],c=z(r.length,i&&i.length||0))},h=0,c=z(r.length,i&&i.length||0);c>h;h++){r[h]=o(r[h],a),u(r,h),i&&(i[h]=o(i[h],s)),i&&u(i,h),l(r,i,a,s,h),l(i,r,s,a,h);var f=r[h],p=i&&i[h],d=f.length,g=i&&p.length;a.x=f[d-2],a.y=f[d-1],a.bx=J(f[d-4])||a.x,a.by=J(f[d-3])||a.y,s.bx=i&&(J(p[g-4])||s.x),s.by=i&&(J(p[g-3])||s.y),s.x=i&&p[g-2],s.y=i&&p[g-1]}return i||(n.curve=Fe(r)),i?[r,i]:r},null,Fe),qe=(e._parseDots=i(function(t){for(var n=[],r=0,i=t.length;i>r;r++){var a={},s=t[r].match(/^([^:]*):?([\d\.]*)/);if(a.color=e.getRGB(s[1]),a.color.error)return null;a.color=a.color.hex,s[2]&&(a.offset=s[2]+"%"),n.push(a)}for(r=1,i=n.length-1;i>r;r++)if(!n[r].offset){for(var o=J(n[r-1].offset||0),u=0,l=r+1;i>l;l++)if(n[l].offset){u=n[l].offset;break}u||(u=100,l=i),u=J(u);for(var h=(u-o)/(l-r+1);l>r;r++)o+=h,n[r].offset=o+"%"}return n}),e._tear=function(t,e){t==e.top&&(e.top=t.prev),t==e.bottom&&(e.bottom=t.next),t.next&&(t.next.prev=t.prev),t.prev&&(t.prev.next=t.next)}),je=(e._tofront=function(t,e){e.top!==t&&(qe(t,e),t.next=null,t.prev=e.top,e.top.next=t,e.top=t)},e._toback=function(t,e){e.bottom!==t&&(qe(t,e),t.next=e.bottom,t.prev=null,e.bottom.prev=t,e.bottom=t)},e._insertafter=function(t,e,n){qe(t,n),e==n.top&&(n.top=t),e.next&&(e.next.prev=t),t.next=e.next,t.prev=e,e.next=t},e._insertbefore=function(t,e,n){qe(t,n),e==n.bottom&&(n.bottom=t),e.prev&&(e.prev.next=t),t.prev=e.prev,e.prev=t,t.next=e},e.toMatrix=function(t,e){var n=Ce(t),r={_:{transform:P},getBBox:function(){return n}};return De(r,e),r.matrix}),De=(e.transformPath=function(t,e){return xe(t,je(t,e))},e._extractTransform=function(t,n){if(null==n)return t._.transform;n=M(n).replace(/\.{3}|\u2026/g,t._.transform||P);var r=e.parseTransformString(n),i=0,a=0,s=0,o=1,u=1,l=t._,h=new p;if(l.transform=r||[],r)for(var c=0,f=r.length;f>c;c++){var d,g,x,v,m,y=r[c],b=y.length,_=M(y[0]).toLowerCase(),w=y[0]!=_,k=w?h.invert():0;"t"==_&&3==b?w?(d=k.x(0,0),g=k.y(0,0),x=k.x(y[1],y[2]),v=k.y(y[1],y[2]),h.translate(x-d,v-g)):h.translate(y[1],y[2]):"r"==_?2==b?(m=m||t.getBBox(1),h.rotate(y[1],m.x+m.width/2,m.y+m.height/2),i+=y[1]):4==b&&(w?(x=k.x(y[2],y[3]),v=k.y(y[2],y[3]),h.rotate(y[1],x,v)):h.rotate(y[1],y[2],y[3]),i+=y[1]):"s"==_?2==b||3==b?(m=m||t.getBBox(1),h.scale(y[1],y[b-1],m.x+m.width/2,m.y+m.height/2),o*=y[1],u*=y[b-1]):5==b&&(w?(x=k.x(y[3],y[4]),v=k.y(y[3],y[4]),h.scale(y[1],y[2],x,v)):h.scale(y[1],y[2],y[3],y[4]),o*=y[1],u*=y[2]):"m"==_&&7==b&&h.add(y[1],y[2],y[3],y[4],y[5],y[6]),l.dirtyT=1,t.matrix=h}t.matrix=h,l.sx=o,l.sy=u,l.deg=i,l.dx=a=h.e,l.dy=s=h.f,1==o&&1==u&&!i&&l.bbox?(l.bbox.x+=+a,l.bbox.y+=+s):l.dirtyT=1}),ze=function(t){var e=t[0];switch(e.toLowerCase()){case"t":return[e,0,0];case"m":return[e,1,0,0,1,0,0];case"r":return 4==t.length?[e,0,t[2],t[3]]:[e,0];case"s":return 5==t.length?[e,1,1,t[3],t[4]]:3==t.length?[e,1,1]:[e,1]}},Oe=e._equaliseTransform=function(t,n){n=M(n).replace(/\.{3}|\u2026/g,t),t=e.parseTransformString(t)||[],n=e.parseTransformString(n)||[];for(var r,i,a,s,o=z(t.length,n.length),u=[],l=[],h=0;o>h;h++){if(a=t[h]||ze(n[h]),s=n[h]||ze(a),a[0]!=s[0]||"r"==a[0].toLowerCase()&&(a[2]!=s[2]||a[3]!=s[3])||"s"==a[0].toLowerCase()&&(a[3]!=s[3]||a[4]!=s[4]))return;for(u[h]=[],l[h]=[],r=0,i=z(a.length,s.length);i>r;r++)r in a&&(u[h][r]=a[r]),r in s&&(l[h][r]=s[r])}return{from:u,to:l}};e._getContainer=function(t,n,r,i){var a;return a=null!=i||e.is(t,"object")?t:S.doc.getElementById(t),null!=a?a.tagName?null==n?{container:a,width:a.style.pixelWidth||a.offsetWidth,height:a.style.pixelHeight||a.offsetHeight}:{container:a,width:n,height:r}:{container:1,x:t,y:n,width:r,height:i}:void 0},e.pathToRelative=Te,e._engine={},e.path2curve=Re,e.matrix=function(t,e,n,r,i,a){return new p(t,e,n,r,i,a)},function(t){function n(t){return t[0]*t[0]+t[1]*t[1]}function r(t){var e=D.sqrt(n(t));t[0]&&(t[0]/=e),t[1]&&(t[1]/=e)}t.add=function(t,e,n,r,i,a){var s,o,u,l,h=[[],[],[]],c=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],f=[[t,n,i],[e,r,a],[0,0,1]];for(t&&t instanceof p&&(f=[[t.a,t.c,t.e],[t.b,t.d,t.f],[0,0,1]]),s=0;3>s;s++)for(o=0;3>o;o++){for(l=0,u=0;3>u;u++)l+=c[s][u]*f[u][o];h[s][o]=l}this.a=h[0][0],this.b=h[1][0],this.c=h[0][1],this.d=h[1][1],this.e=h[0][2],this.f=h[1][2]},t.invert=function(){var t=this,e=t.a*t.d-t.b*t.c;return new p(t.d/e,-t.b/e,-t.c/e,t.a/e,(t.c*t.f-t.d*t.e)/e,(t.b*t.e-t.a*t.f)/e)},t.clone=function(){return new p(this.a,this.b,this.c,this.d,this.e,this.f)},t.translate=function(t,e){this.add(1,0,0,1,t,e)},t.scale=function(t,e,n,r){null==e&&(e=t),(n||r)&&this.add(1,0,0,1,n,r),this.add(t,0,0,e,0,0),(n||r)&&this.add(1,0,0,1,-n,-r)},t.rotate=function(t,n,r){t=e.rad(t),n=n||0,r=r||0;var i=+D.cos(t).toFixed(9),a=+D.sin(t).toFixed(9);this.add(i,a,-a,i,n,r),this.add(1,0,0,1,-n,-r)},t.x=function(t,e){return t*this.a+e*this.c+this.e},t.y=function(t,e){return t*this.b+e*this.d+this.f},t.get=function(t){return+this[M.fromCharCode(97+t)].toFixed(4)},t.toString=function(){return e.svg?"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")":[this.get(0),this.get(2),this.get(1),this.get(3),0,0].join()},t.toFilter=function(){return"progid:DXImageTransform.Microsoft.Matrix(M11="+this.get(0)+", M12="+this.get(2)+", M21="+this.get(1)+", M22="+this.get(3)+", Dx="+this.get(4)+", Dy="+this.get(5)+", sizingmethod='auto expand')"},t.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},t.split=function(){var t={};t.dx=this.e,t.dy=this.f;var i=[[this.a,this.c],[this.b,this.d]];t.scalex=D.sqrt(n(i[0])),r(i[0]),t.shear=i[0][0]*i[1][0]+i[0][1]*i[1][1],i[1]=[i[1][0]-i[0][0]*t.shear,i[1][1]-i[0][1]*t.shear],t.scaley=D.sqrt(n(i[1])),r(i[1]),t.shear/=t.scaley;var a=-i[0][1],s=i[1][1];return 0>s?(t.rotate=e.deg(D.acos(s)),0>a&&(t.rotate=360-t.rotate)):t.rotate=e.deg(D.asin(a)),t.isSimple=!(+t.shear.toFixed(9)||t.scalex.toFixed(9)!=t.scaley.toFixed(9)&&t.rotate),t.isSuperSimple=!+t.shear.toFixed(9)&&t.scalex.toFixed(9)==t.scaley.toFixed(9)&&!t.rotate,t.noRotation=!+t.shear.toFixed(9)&&!t.rotate,t -},t.toTransformString=function(t){var e=t||this[I]();return e.isSimple?(e.scalex=+e.scalex.toFixed(4),e.scaley=+e.scaley.toFixed(4),e.rotate=+e.rotate.toFixed(4),(e.dx||e.dy?"t"+[e.dx,e.dy]:P)+(1!=e.scalex||1!=e.scaley?"s"+[e.scalex,e.scaley,0,0]:P)+(e.rotate?"r"+[e.rotate,0,0]:P)):"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]}}(p.prototype);var Ve=navigator.userAgent.match(/Version\/(.*?)\s/)||navigator.userAgent.match(/Chrome\/(\d+)/);b.safari="Apple Computer, Inc."==navigator.vendor&&(Ve&&4>Ve[1]||"iP"==navigator.platform.slice(0,2))||"Google Inc."==navigator.vendor&&Ve&&8>Ve[1]?function(){var t=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});setTimeout(function(){t.remove()})}:ce;for(var Xe=function(){this.returnValue=!1},Ye=function(){return this.originalEvent.preventDefault()},Ge=function(){this.cancelBubble=!0},Ne=function(){return this.originalEvent.stopPropagation()},We=function(){return S.doc.addEventListener?function(t,e,n,r){var i=A&&q[e]?q[e]:e,a=function(i){var a=S.doc.documentElement.scrollTop||S.doc.body.scrollTop,s=S.doc.documentElement.scrollLeft||S.doc.body.scrollLeft,o=i.clientX+s,u=i.clientY+a;if(A&&q[B](e))for(var l=0,h=i.targetTouches&&i.targetTouches.length;h>l;l++)if(i.targetTouches[l].target==t){var c=i;i=i.targetTouches[l],i.originalEvent=c,i.preventDefault=Ye,i.stopPropagation=Ne;break}return n.call(r,i,o,u)};return t.addEventListener(i,a,!1),function(){return t.removeEventListener(i,a,!1),!0}}:S.doc.attachEvent?function(t,e,n,r){var i=function(t){t=t||S.win.event;var e=S.doc.documentElement.scrollTop||S.doc.body.scrollTop,i=S.doc.documentElement.scrollLeft||S.doc.body.scrollLeft,a=t.clientX+i,s=t.clientY+e;return t.preventDefault=t.preventDefault||Xe,t.stopPropagation=t.stopPropagation||Ge,n.call(r,t,a,s)};t.attachEvent("on"+e,i);var a=function(){return t.detachEvent("on"+e,i),!0};return a}:void 0}(),$e=[],He=function(e){for(var n,r=e.clientX,i=e.clientY,a=S.doc.documentElement.scrollTop||S.doc.body.scrollTop,s=S.doc.documentElement.scrollLeft||S.doc.body.scrollLeft,o=$e.length;o--;){if(n=$e[o],A){for(var u,l=e.touches.length;l--;)if(u=e.touches[l],u.identifier==n.el._drag.id){r=u.clientX,i=u.clientY,(e.originalEvent?e.originalEvent:e).preventDefault();break}}else e.preventDefault();var h,c=n.el.node,f=c.nextSibling,p=c.parentNode,d=c.style.display;S.win.opera&&p.removeChild(c),c.style.display="none",h=n.el.paper.getElementByPoint(r,i),c.style.display=d,S.win.opera&&(f?p.insertBefore(c,f):p.appendChild(c)),h&&t("raphael.drag.over."+n.el.id,n.el,h),r+=s,i+=a,t("raphael.drag.move."+n.el.id,n.move_scope||n.el,r-n.el._drag.x,i-n.el._drag.y,r,i,e)}},Ue=function(n){e.unmousemove(He).unmouseup(Ue);for(var r,i=$e.length;i--;)r=$e[i],r.el._drag={},t("raphael.drag.end."+r.el.id,r.end_scope||r.start_scope||r.move_scope||r.el,n);$e=[]},Ze=e.el={},Qe=R.length;Qe--;)(function(t){e[t]=Ze[t]=function(n,r){return e.is(n,"function")&&(this.events=this.events||[],this.events.push({name:t,f:n,unbind:We(this.shape||this.node||S.doc,t,n,r||this)})),this},e["un"+t]=Ze["un"+t]=function(e){for(var n=this.events||[],r=n.length;r--;)if(n[r].name==t&&n[r].f==e)return n[r].unbind(),n.splice(r,1),!n.length&&delete this.events,this;return this}})(R[Qe]);Ze.data=function(n,r){var i=le[this.id]=le[this.id]||{};if(1==arguments.length){if(e.is(n,"object")){for(var a in n)n[B](a)&&this.data(a,n[a]);return this}return t("raphael.data.get."+this.id,this,i[n],n),i[n]}return i[n]=r,t("raphael.data.set."+this.id,this,r,n),this},Ze.removeData=function(t){return null==t?le[this.id]={}:le[this.id]&&delete le[this.id][t],this},Ze.getData=function(){return n(le[this.id]||{})},Ze.hover=function(t,e,n,r){return this.mouseover(t,n).mouseout(e,r||n)},Ze.unhover=function(t,e){return this.unmouseover(t).unmouseout(e)};var Je=[];Ze.drag=function(n,r,i,a,s,o){function u(u){(u.originalEvent||u).preventDefault();var l=S.doc.documentElement.scrollTop||S.doc.body.scrollTop,h=S.doc.documentElement.scrollLeft||S.doc.body.scrollLeft;this._drag.x=u.clientX+h,this._drag.y=u.clientY+l,this._drag.id=u.identifier,!$e.length&&e.mousemove(He).mouseup(Ue),$e.push({el:this,move_scope:a,start_scope:s,end_scope:o}),r&&t.on("raphael.drag.start."+this.id,r),n&&t.on("raphael.drag.move."+this.id,n),i&&t.on("raphael.drag.end."+this.id,i),t("raphael.drag.start."+this.id,s||a||this,u.clientX+h,u.clientY+l,u)}return this._drag={},Je.push({el:this,start:u}),this.mousedown(u),this},Ze.onDragOver=function(e){e?t.on("raphael.drag.over."+this.id,e):t.unbind("raphael.drag.over."+this.id)},Ze.undrag=function(){for(var n=Je.length;n--;)Je[n].el==this&&(this.unmousedown(Je[n].start),Je.splice(n,1),t.unbind("raphael.drag.*."+this.id));!Je.length&&e.unmousemove(He).unmouseup(Ue),$e=[]},b.circle=function(t,n,r){var i=e._engine.circle(this,t||0,n||0,r||0);return this.__set__&&this.__set__.push(i),i},b.rect=function(t,n,r,i,a){var s=e._engine.rect(this,t||0,n||0,r||0,i||0,a||0);return this.__set__&&this.__set__.push(s),s},b.ellipse=function(t,n,r,i){var a=e._engine.ellipse(this,t||0,n||0,r||0,i||0);return this.__set__&&this.__set__.push(a),a},b.path=function(t){t&&!e.is(t,N)&&!e.is(t[0],W)&&(t+=P);var n=e._engine.path(e.format[T](e,arguments),this);return this.__set__&&this.__set__.push(n),n},b.image=function(t,n,r,i,a){var s=e._engine.image(this,t||"about:blank",n||0,r||0,i||0,a||0);return this.__set__&&this.__set__.push(s),s},b.text=function(t,n,r){var i=e._engine.text(this,t||0,n||0,M(r));return this.__set__&&this.__set__.push(i),i},b.set=function(t){!e.is(t,"array")&&(t=Array.prototype.splice.call(arguments,0,arguments.length));var n=new cn(t);return this.__set__&&this.__set__.push(n),n.paper=this,n.type="set",n},b.setStart=function(t){this.__set__=t||this.set()},b.setFinish=function(){var t=this.__set__;return delete this.__set__,t},b.setSize=function(t,n){return e._engine.setSize.call(this,t,n)},b.setViewBox=function(t,n,r,i,a){return e._engine.setViewBox.call(this,t,n,r,i,a)},b.top=b.bottom=null,b.raphael=e;var Ke=function(t){var e=t.getBoundingClientRect(),n=t.ownerDocument,r=n.body,i=n.documentElement,a=i.clientTop||r.clientTop||0,s=i.clientLeft||r.clientLeft||0,o=e.top+(S.win.pageYOffset||i.scrollTop||r.scrollTop)-a,u=e.left+(S.win.pageXOffset||i.scrollLeft||r.scrollLeft)-s;return{y:o,x:u}};b.getElementByPoint=function(t,e){var n=this,r=n.canvas,i=S.doc.elementFromPoint(t,e);if(S.win.opera&&"svg"==i.tagName){var a=Ke(r),s=r.createSVGRect();s.x=t-a.x,s.y=e-a.y,s.width=s.height=1;var o=r.getIntersectionList(s,null);o.length&&(i=o[o.length-1])}if(!i)return null;for(;i.parentNode&&i!=r.parentNode&&!i.raphael;)i=i.parentNode;return i==n.canvas.parentNode&&(i=r),i=i&&i.raphael?n.getById(i.raphaelid):null},b.getElementsByBBox=function(t){var n=this.set();return this.forEach(function(r){e.isBBoxIntersect(r.getBBox(),t)&&n.push(r)}),n},b.getById=function(t){for(var e=this.bottom;e;){if(e.id==t)return e;e=e.next}return null},b.forEach=function(t,e){for(var n=this.bottom;n;){if(t.call(e,n)===!1)return this;n=n.next}return this},b.getElementsByPoint=function(t,e){var n=this.set();return this.forEach(function(r){r.isPointInside(t,e)&&n.push(r)}),n},Ze.isPointInside=function(t,n){var r=this.realPath=this.realPath||ge[this.type](this);return e.isPointInsidePath(r,t,n)},Ze.getBBox=function(t){if(this.removed)return{};var e=this._;return t?((e.dirty||!e.bboxwt)&&(this.realPath=ge[this.type](this),e.bboxwt=Ce(this.realPath),e.bboxwt.toString=d,e.dirty=0),e.bboxwt):((e.dirty||e.dirtyT||!e.bbox)&&((e.dirty||!this.realPath)&&(e.bboxwt=0,this.realPath=ge[this.type](this)),e.bbox=Ce(xe(this.realPath,this.matrix)),e.bbox.toString=d,e.dirty=e.dirtyT=0),e.bbox)},Ze.clone=function(){if(this.removed)return null;var t=this.paper[this.type]().attr(this.attr());return this.__set__&&this.__set__.push(t),t},Ze.glow=function(t){if("text"==this.type)return null;t=t||{};var e={width:(t.width||10)+(+this.attr("stroke-width")||1),fill:t.fill||!1,opacity:t.opacity||.5,offsetx:t.offsetx||0,offsety:t.offsety||0,color:t.color||"#000"},n=e.width/2,r=this.paper,i=r.set(),a=this.realPath||ge[this.type](this);a=this.matrix?xe(a,this.matrix):a;for(var s=1;n+1>s;s++)i.push(r.path(a).attr({stroke:e.color,fill:e.fill?e.color:"none","stroke-linejoin":"round","stroke-linecap":"round","stroke-width":+(e.width/n*s).toFixed(3),opacity:+(e.opacity/n).toFixed(3)}));return i.insertBefore(this).translate(e.offsetx,e.offsety)};var tn=function(t,n,r,i,a,s,o,h,c){return null==c?u(t,n,r,i,a,s,o,h):e.findDotsAtSegment(t,n,r,i,a,s,o,h,l(t,n,r,i,a,s,o,h,c))},en=function(t,n){return function(r,i,a){r=Re(r);for(var s,o,u,l,h,c="",f={},p=0,d=0,g=r.length;g>d;d++){if(u=r[d],"M"==u[0])s=+u[1],o=+u[2];else{if(l=tn(s,o,u[1],u[2],u[3],u[4],u[5],u[6]),p+l>i){if(n&&!f.start){if(h=tn(s,o,u[1],u[2],u[3],u[4],u[5],u[6],i-p),c+=["C"+h.start.x,h.start.y,h.m.x,h.m.y,h.x,h.y],a)return c;f.start=c,c=["M"+h.x,h.y+"C"+h.n.x,h.n.y,h.end.x,h.end.y,u[5],u[6]].join(),p+=l,s=+u[5],o=+u[6];continue}if(!t&&!n)return h=tn(s,o,u[1],u[2],u[3],u[4],u[5],u[6],i-p),{x:h.x,y:h.y,alpha:h.alpha}}p+=l,s=+u[5],o=+u[6]}c+=u.shift()+u}return f.end=c,h=t?p:n?f:e.findDotsAtSegment(s,o,u[0],u[1],u[2],u[3],u[4],u[5],1),h.alpha&&(h={x:h.x,y:h.y,alpha:h.alpha}),h}},nn=en(1),rn=en(),an=en(0,1);e.getTotalLength=nn,e.getPointAtLength=rn,e.getSubpath=function(t,e,n){if(1e-6>this.getTotalLength(t)-n)return an(t,e).end;var r=an(t,n,1);return e?an(r,e).end:r},Ze.getTotalLength=function(){return"path"==this.type?this.node.getTotalLength?this.node.getTotalLength():nn(this.attrs.path):void 0},Ze.getPointAtLength=function(t){return"path"==this.type?rn(this.attrs.path,t):void 0},Ze.getSubpath=function(t,n){return"path"==this.type?e.getSubpath(this.attrs.path,t,n):void 0};var sn=e.easing_formulas={linear:function(t){return t},"<":function(t){return X(t,1.7)},">":function(t){return X(t,.48)},"<>":function(t){var e=.48-t/1.04,n=D.sqrt(.1734+e*e),r=n-e,i=X(V(r),1/3)*(0>r?-1:1),a=-n-e,s=X(V(a),1/3)*(0>a?-1:1),o=i+s+.5;return 3*(1-o)*o*o+o*o*o},backIn:function(t){var e=1.70158;return t*t*((e+1)*t-e)},backOut:function(t){t-=1;var e=1.70158;return t*t*((e+1)*t+e)+1},elastic:function(t){return t==!!t?t:X(2,-10*t)*D.sin((t-.075)*2*Y/.3)+1},bounce:function(t){var e,n=7.5625,r=2.75;return 1/r>t?e=n*t*t:2/r>t?(t-=1.5/r,e=n*t*t+.75):2.5/r>t?(t-=2.25/r,e=n*t*t+.9375):(t-=2.625/r,e=n*t*t+.984375),e}};sn.easeIn=sn["ease-in"]=sn["<"],sn.easeOut=sn["ease-out"]=sn[">"],sn.easeInOut=sn["ease-in-out"]=sn["<>"],sn["back-in"]=sn.backIn,sn["back-out"]=sn.backOut;var on=[],un=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){setTimeout(t,16)},ln=function(){for(var n=+new Date,r=0;on.length>r;r++){var i=on[r];if(!i.el.removed&&!i.paused){var a,s,o=n-i.start,u=i.ms,l=i.easing,h=i.from,c=i.diff,f=i.to,p=(i.t,i.el),d={},g={};if(i.initstatus?(o=(i.initstatus*i.anim.top-i.prev)/(i.percent-i.prev)*u,i.status=i.initstatus,delete i.initstatus,i.stop&&on.splice(r--,1)):i.status=(i.prev+(i.percent-i.prev)*(o/u))/i.anim.top,!(0>o))if(u>o){var x=l(o/u);for(var m in h)if(h[B](m)){switch(ne[m]){case G:a=+h[m]+x*u*c[m];break;case"colour":a="rgb("+[hn(Q(h[m].r+x*u*c[m].r)),hn(Q(h[m].g+x*u*c[m].g)),hn(Q(h[m].b+x*u*c[m].b))].join(",")+")";break;case"path":a=[];for(var y=0,b=h[m].length;b>y;y++){a[y]=[h[m][y][0]];for(var _=1,w=h[m][y].length;w>_;_++)a[y][_]=+h[m][y][_]+x*u*c[m][y][_];a[y]=a[y].join(E)}a=a.join(E);break;case"transform":if(c[m].real)for(a=[],y=0,b=h[m].length;b>y;y++)for(a[y]=[h[m][y][0]],_=1,w=h[m][y].length;w>_;_++)a[y][_]=h[m][y][_]+x*u*c[m][y][_];else{var k=function(t){return+h[m][t]+x*u*c[m][t]};a=[["m",k(0),k(1),k(2),k(3),k(4),k(5)]]}break;case"csv":if("clip-rect"==m)for(a=[],y=4;y--;)a[y]=+h[m][y]+x*u*c[m][y];break;default:var S=[][L](h[m]);for(a=[],y=p.paper.customAttributes[m].length;y--;)a[y]=+S[y]+x*u*c[m][y]}d[m]=a}p.attr(d),function(e,n,r){setTimeout(function(){t("raphael.anim.frame."+e,n,r)})}(p.id,p,i.anim)}else{if(function(n,r,i){setTimeout(function(){t("raphael.anim.frame."+r.id,r,i),t("raphael.anim.finish."+r.id,r,i),e.is(n,"function")&&n.call(r)})}(i.callback,p,i.anim),p.attr(f),on.splice(r--,1),i.repeat>1&&!i.next){for(s in f)f[B](s)&&(g[s]=i.totalOrigin[s]);i.el.attr(g),v(i.anim,i.el,i.anim.percents[0],null,i.totalOrigin,i.repeat-1)}i.next&&!i.stop&&v(i.anim,i.el,i.next,null,i.totalOrigin,i.repeat)}}}e.svg&&p&&p.paper&&p.paper.safari(),on.length&&un(ln)},hn=function(t){return t>255?255:0>t?0:t};Ze.animateWith=function(t,n,r,i,a,s){var o=this;if(o.removed)return s&&s.call(o),o;var u=r instanceof x?r:e.animation(r,i,a,s);v(u,o,u.percents[0],null,o.attr());for(var l=0,h=on.length;h>l;l++)if(on[l].anim==n&&on[l].el==t){on[h-1].start=on[l].start;break}return o},Ze.onAnimation=function(e){return e?t.on("raphael.anim.frame."+this.id,e):t.unbind("raphael.anim.frame."+this.id),this},x.prototype.delay=function(t){var e=new x(this.anim,this.ms);return e.times=this.times,e.del=+t||0,e},x.prototype.repeat=function(t){var e=new x(this.anim,this.ms);return e.del=this.del,e.times=D.floor(z(t,0))||1,e},e.animation=function(t,n,r,i){if(t instanceof x)return t;(e.is(r,"function")||!r)&&(i=i||r||null,r=null),t=Object(t),n=+n||0;var a,s,o={};for(s in t)t[B](s)&&J(s)!=s&&J(s)+"%"!=s&&(a=!0,o[s]=t[s]);return a?(r&&(o.easing=r),i&&(o.callback=i),new x({100:o},n)):new x(t,n)},Ze.animate=function(t,n,r,i){var a=this;if(a.removed)return i&&i.call(a),a;var s=t instanceof x?t:e.animation(t,n,r,i);return v(s,a,s.percents[0],null,a.attr()),a},Ze.setTime=function(t,e){return t&&null!=e&&this.status(t,O(e,t.ms)/t.ms),this},Ze.status=function(t,e){var n,r,i=[],a=0;if(null!=e)return v(t,this,-1,O(e,1)),this;for(n=on.length;n>a;a++)if(r=on[a],r.el.id==this.id&&(!t||r.anim==t)){if(t)return r.status;i.push({anim:r.anim,status:r.status})}return t?0:i},Ze.pause=function(e){for(var n=0;on.length>n;n++)on[n].el.id!=this.id||e&&on[n].anim!=e||t("raphael.anim.pause."+this.id,this,on[n].anim)!==!1&&(on[n].paused=!0);return this},Ze.resume=function(e){for(var n=0;on.length>n;n++)if(on[n].el.id==this.id&&(!e||on[n].anim==e)){var r=on[n];t("raphael.anim.resume."+this.id,this,r.anim)!==!1&&(delete r.paused,this.status(r.anim,r.status))}return this},Ze.stop=function(e){for(var n=0;on.length>n;n++)on[n].el.id!=this.id||e&&on[n].anim!=e||t("raphael.anim.stop."+this.id,this,on[n].anim)!==!1&&on.splice(n--,1);return this},t.on("raphael.remove",m),t.on("raphael.clear",m),Ze.toString=function(){return"Raphaël’s object"};var cn=function(t){if(this.items=[],this.length=0,this.type="set",t)for(var e=0,n=t.length;n>e;e++)!t[e]||t[e].constructor!=Ze.constructor&&t[e].constructor!=cn||(this[this.items.length]=this.items[this.items.length]=t[e],this.length++)},fn=cn.prototype;fn.push=function(){for(var t,e,n=0,r=arguments.length;r>n;n++)t=arguments[n],!t||t.constructor!=Ze.constructor&&t.constructor!=cn||(e=this.items.length,this[e]=this.items[e]=t,this.length++);return this},fn.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},fn.forEach=function(t,e){for(var n=0,r=this.items.length;r>n;n++)if(t.call(e,this.items[n],n)===!1)return this;return this};for(var pn in Ze)Ze[B](pn)&&(fn[pn]=function(t){return function(){var e=arguments;return this.forEach(function(n){n[t][T](n,e)})}}(pn));return fn.attr=function(t,n){if(t&&e.is(t,W)&&e.is(t[0],"object"))for(var r=0,i=t.length;i>r;r++)this.items[r].attr(t[r]);else for(var a=0,s=this.items.length;s>a;a++)this.items[a].attr(t,n);return this},fn.clear=function(){for(;this.length;)this.pop()},fn.splice=function(t,e){t=0>t?z(this.length+t,0):t,e=z(0,O(this.length-t,e));var n,r=[],i=[],a=[];for(n=2;arguments.length>n;n++)a.push(arguments[n]);for(n=0;e>n;n++)i.push(this[t+n]);for(;this.length-t>n;n++)r.push(this[t+n]);var s=a.length;for(n=0;s+r.length>n;n++)this.items[t+n]=this[t+n]=s>n?a[n]:r[n-s];for(n=this.items.length=this.length-=e-s;this[n];)delete this[n++];return new cn(i)},fn.exclude=function(t){for(var e=0,n=this.length;n>e;e++)if(this[e]==t)return this.splice(e,1),!0},fn.animate=function(t,n,r,i){(e.is(r,"function")||!r)&&(i=r||null);var a,s,o=this.items.length,u=o,l=this;if(!o)return this;i&&(s=function(){!--o&&i.call(l)}),r=e.is(r,N)?r:s;var h=e.animation(t,n,r,s);for(a=this.items[--u].animate(h);u--;)this.items[u]&&!this.items[u].removed&&this.items[u].animateWith(a,h,h);return this},fn.insertAfter=function(t){for(var e=this.items.length;e--;)this.items[e].insertAfter(t);return this},fn.getBBox=function(){for(var t=[],e=[],n=[],r=[],i=this.items.length;i--;)if(!this.items[i].removed){var a=this.items[i].getBBox();t.push(a.x),e.push(a.y),n.push(a.x+a.width),r.push(a.y+a.height)}return t=O[T](0,t),e=O[T](0,e),n=z[T](0,n),r=z[T](0,r),{x:t,y:e,x2:n,y2:r,width:n-t,height:r-e}},fn.clone=function(t){t=this.paper.set();for(var e=0,n=this.items.length;n>e;e++)t.push(this.items[e].clone());return t},fn.toString=function(){return"Raphaël‘s set"},fn.glow=function(t){var e=this.paper.set();return this.forEach(function(n){var r=n.glow(t);null!=r&&r.forEach(function(t){e.push(t)})}),e},e.registerFont=function(t){if(!t.face)return t;this.fonts=this.fonts||{};var e={w:t.w,face:{},glyphs:{}},n=t.face["font-family"];for(var r in t.face)t.face[B](r)&&(e.face[r]=t.face[r]);if(this.fonts[n]?this.fonts[n].push(e):this.fonts[n]=[e],!t.svg){e.face["units-per-em"]=K(t.face["units-per-em"],10);for(var i in t.glyphs)if(t.glyphs[B](i)){var a=t.glyphs[i];if(e.glyphs[i]={w:a.w,k:{},d:a.d&&"M"+a.d.replace(/[mlcxtrv]/g,function(t){return{l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"}[t]||"M"})+"z"},a.k)for(var s in a.k)a[B](s)&&(e.glyphs[i].k[s]=a.k[s])}}return t},b.getFont=function(t,n,r,i){if(i=i||"normal",r=r||"normal",n=+n||{normal:400,bold:700,lighter:300,bolder:800}[n]||400,e.fonts){var a=e.fonts[t];if(!a){var s=RegExp("(^|\\s)"+t.replace(/[^\w\d\s+!~.:_-]/g,P)+"(\\s|$)","i");for(var o in e.fonts)if(e.fonts[B](o)&&s.test(o)){a=e.fonts[o];break}}var u;if(a)for(var l=0,h=a.length;h>l&&(u=a[l],u.face["font-weight"]!=n||u.face["font-style"]!=r&&u.face["font-style"]||u.face["font-stretch"]!=i);l++);return u}},b.print=function(t,n,r,i,a,s,o){s=s||"middle",o=z(O(o||0,1),-1);var u,l=M(r)[I](P),h=0,c=0,f=P;if(e.is(i,"string")&&(i=this.getFont(i)),i){u=(a||16)/i.face["units-per-em"];for(var p=i.face.bbox[I](_),d=+p[0],g=p[3]-p[1],x=0,v=+p[1]+("baseline"==s?g+ +i.face.descent:g/2),m=0,y=l.length;y>m;m++){if("\n"==l[m])h=0,w=0,c=0,x+=g;else{var b=c&&i.glyphs[l[m-1]]||{},w=i.glyphs[l[m]];h+=c?(b.w||i.w)+(b.k&&b.k[l[m]]||0)+i.w*o:0,c=1}w&&w.d&&(f+=e.transformPath(w.d,["t",h*u,x*u,"s",u,u,d,v,"t",(t-d)/u,(n-v)/u]))}}return this.path(f).attr({fill:"#000",stroke:"none"})},b.add=function(t){if(e.is(t,"array"))for(var n,r=this.set(),i=0,a=t.length;a>i;i++)n=t[i]||{},w[B](n.type)&&r.push(this[n.type]().attr(n));return r},e.format=function(t,n){var r=e.is(n,W)?[0][L](n):arguments;return t&&e.is(t,N)&&r.length-1&&(t=t.replace(k,function(t,e){return null==r[++e]?P:r[e]})),t||P},e.fullfill=function(){var t=/\{([^\}]+)\}/g,e=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,n=function(t,n,r){var i=r;return n.replace(e,function(t,e,n,r,a){e=e||r,i&&(e in i&&(i=i[e]),"function"==typeof i&&a&&(i=i()))}),i=(null==i||i==r?t:i)+""};return function(e,r){return(e+"").replace(t,function(t,e){return n(t,e,r)})}}(),e.ninja=function(){return C.was?S.win.Raphael=C.is:delete Raphael,e},e.st=fn,function(t,n,r){function i(){/in/.test(t.readyState)?setTimeout(i,9):e.eve("raphael.DOMload")}null==t.readyState&&t.addEventListener&&(t.addEventListener(n,r=function(){t.removeEventListener(n,r,!1),t.readyState="complete"},!1),t.readyState="loading"),i()}(document,"DOMContentLoaded"),C.was?S.win.Raphael=e:Raphael=e,t.on("raphael.DOMload",function(){y=!0}),e});(function(t,e){"function"==typeof define&&define.amd?require(["raphael"],e):t.Raphael&&e(t.Raphael)})(this,function(t){if(t.svg){var e="hasOwnProperty",r=String,n=parseFloat,i=parseInt,a=Math,s=a.max,o=a.abs,u=a.pow,h=/[, ]+/,l=t.eve,c="",f=" ",p="http://www.w3.org/1999/xlink",d={block:"M5,0 0,2.5 5,5z",classic:"M5,0 0,2.5 5,5 3.5,3 3.5,2z",diamond:"M2.5,0 5,2.5 2.5,5 0,2.5z",open:"M6,1 1,3.5 6,6",oval:"M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"},g={};t.toString=function(){return"Your browser supports SVG.\nYou are running Raphaël "+this.version};var x=function(n,i){if(i){"string"==typeof n&&(n=x(n));for(var a in i)i[e](a)&&("xlink:"==a.substring(0,6)?n.setAttributeNS(p,a.substring(6),r(i[a])):n.setAttribute(a,r(i[a])))}else n=t._g.doc.createElementNS("http://www.w3.org/2000/svg",n),n.style&&(n.style.webkitTapHighlightColor="rgba(0,0,0,0)");return n},v=function(e,i){var h="linear",l=e.id+i,f=.5,p=.5,d=e.node,g=e.paper,v=d.style,y=t._g.doc.getElementById(l);if(!y){if(i=r(i).replace(t._radial_gradient,function(t,e,r){if(h="radial",e&&r){f=n(e),p=n(r);var i=2*(p>.5)-1;u(f-.5,2)+u(p-.5,2)>.25&&(p=a.sqrt(.25-u(f-.5,2))*i+.5)&&.5!=p&&(p=p.toFixed(5)-1e-5*i)}return c}),i=i.split(/\s*\-\s*/),"linear"==h){var m=i.shift();if(m=-n(m),isNaN(m))return null;var b=[0,0,a.cos(t.rad(m)),a.sin(t.rad(m))],_=1/(s(o(b[2]),o(b[3]))||1);b[2]*=_,b[3]*=_,0>b[2]&&(b[0]=-b[2],b[2]=0),0>b[3]&&(b[1]=-b[3],b[3]=0)}var w=t._parseDots(i);if(!w)return null;if(l=l.replace(/[\(\)\s,\xb0#]/g,"_"),e.gradient&&l!=e.gradient.id&&(g.defs.removeChild(e.gradient),delete e.gradient),!e.gradient){y=x(h+"Gradient",{id:l}),e.gradient=y,x(y,"radial"==h?{fx:f,fy:p}:{x1:b[0],y1:b[1],x2:b[2],y2:b[3],gradientTransform:e.matrix.invert()}),g.defs.appendChild(y);for(var k=0,C=w.length;C>k;k++)y.appendChild(x("stop",{offset:w[k].offset?w[k].offset:k?"100%":"0%","stop-color":w[k].color||"#fff"}))}}return x(d,{fill:"url(#"+l+")",opacity:1,"fill-opacity":1}),v.fill=c,v.opacity=1,v.fillOpacity=1,1},y=function(t){var e=t.getBBox(1);x(t.pattern,{patternTransform:t.matrix.invert()+" translate("+e.x+","+e.y+")"})},m=function(n,i,a){if("path"==n.type){for(var s,o,u,h,l,f=r(i).toLowerCase().split("-"),p=n.paper,v=a?"end":"start",y=n.node,m=n.attrs,b=m["stroke-width"],_=f.length,w="classic",k=3,C=3,B=5;_--;)switch(f[_]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":w=f[_];break;case"wide":C=5;break;case"narrow":C=2;break;case"long":k=5;break;case"short":k=2}if("open"==w?(k+=2,C+=2,B+=2,u=1,h=a?4:1,l={fill:"none",stroke:m.stroke}):(h=u=k/2,l={fill:m.stroke,stroke:"none"}),n._.arrows?a?(n._.arrows.endPath&&g[n._.arrows.endPath]--,n._.arrows.endMarker&&g[n._.arrows.endMarker]--):(n._.arrows.startPath&&g[n._.arrows.startPath]--,n._.arrows.startMarker&&g[n._.arrows.startMarker]--):n._.arrows={},"none"!=w){var S="raphael-marker-"+w,A="raphael-marker-"+v+w+k+C+(m.stroke || "").replace(/\(|,|\)/g, "");t._g.doc.getElementById(S)?g[S]++:(p.defs.appendChild(x(x("path"),{"stroke-linecap":"round",d:d[w],id:S})),g[S]=1);var T,M=t._g.doc.getElementById(A);M?(g[A]++,T=M.getElementsByTagName("use")[0]):(M=x(x("marker"),{id:A,markerHeight:C,markerWidth:k,orient:"auto",refX:h,refY:C/2}),T=x(x("use"),{"xlink:href":"#"+S,transform:(a?"rotate(180 "+k/2+" "+C/2+") ":c)+"scale("+k/B+","+C/B+")","stroke-width":(1/((k/B+C/B)/2)).toFixed(4)}),M.appendChild(T),p.defs.appendChild(M),g[A]=1),x(T,l);var F=u*("diamond"!=w&&"oval"!=w);a?(s=n._.arrows.startdx*b||0,o=t.getTotalLength(m.path)-F*b):(s=F*b,o=t.getTotalLength(m.path)-(n._.arrows.enddx*b||0)),l={},l["marker-"+v]="url(#"+A+")",(o||s)&&(l.d=Raphael.getSubpath(m.path,s,o)),x(y,l),n._.arrows[v+"Path"]=S,n._.arrows[v+"Marker"]=A,n._.arrows[v+"dx"]=F,n._.arrows[v+"Type"]=w,n._.arrows[v+"String"]=i}else a?(s=n._.arrows.startdx*b||0,o=t.getTotalLength(m.path)-s):(s=0,o=t.getTotalLength(m.path)-(n._.arrows.enddx*b||0)),n._.arrows[v+"Path"]&&x(y,{d:Raphael.getSubpath(m.path,s,o)}),delete n._.arrows[v+"Path"],delete n._.arrows[v+"Marker"],delete n._.arrows[v+"dx"],delete n._.arrows[v+"Type"],delete n._.arrows[v+"String"];for(l in g)if(g[e](l)&&!g[l]){var L=t._g.doc.getElementById(l);L&&L.parentNode.removeChild(L)}}},b={"":[0],none:[0],"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},_=function(t,e,n){if(e=b[r(e).toLowerCase()]){for(var i=t.attrs["stroke-width"]||"1",a={round:i,square:i,butt:0}[t.attrs["stroke-linecap"]||n["stroke-linecap"]]||0,s=[],o=e.length;o--;)s[o]=e[o]*i+(o%2?1:-1)*a;x(t.node,{"stroke-dasharray":s.join(",")})}},w=function(n,a){var u=n.node,l=n.attrs,f=u.style.visibility;u.style.visibility="hidden";for(var d in a)if(a[e](d)){if(!t._availableAttrs[e](d))continue;var g=a[d];switch(l[d]=g,d){case"blur":n.blur(g);break;case"href":case"title":case"target":var b=u.parentNode;if("a"!=b.tagName.toLowerCase()){var w=x("a");b.insertBefore(w,u),w.appendChild(u),b=w}"target"==d?b.setAttributeNS(p,"show","blank"==g?"new":g):b.setAttributeNS(p,d,g);break;case"cursor":u.style.cursor=g;break;case"transform":n.transform(g);break;case"arrow-start":m(n,g);break;case"arrow-end":m(n,g,1);break;case"clip-rect":var k=r(g).split(h);if(4==k.length){n.clip&&n.clip.parentNode.parentNode.removeChild(n.clip.parentNode);var B=x("clipPath"),S=x("rect");B.id=t.createUUID(),x(S,{x:k[0],y:k[1],width:k[2],height:k[3]}),B.appendChild(S),n.paper.defs.appendChild(B),x(u,{"clip-path":"url(#"+B.id+")"}),n.clip=S}if(!g){var A=u.getAttribute("clip-path");if(A){var T=t._g.doc.getElementById(A.replace(/(^url\(#|\)$)/g,c));T&&T.parentNode.removeChild(T),x(u,{"clip-path":c}),delete n.clip}}break;case"path":"path"==n.type&&(x(u,{d:g?l.path=t._pathToAbsolute(g):"M0,0"}),n._.dirty=1,n._.arrows&&("startString"in n._.arrows&&m(n,n._.arrows.startString),"endString"in n._.arrows&&m(n,n._.arrows.endString,1)));break;case"width":if(u.setAttribute(d,g),n._.dirty=1,!l.fx)break;d="x",g=l.x;case"x":l.fx&&(g=-l.x-(l.width||0));case"rx":if("rx"==d&&"rect"==n.type)break;case"cx":u.setAttribute(d,g),n.pattern&&y(n),n._.dirty=1;break;case"height":if(u.setAttribute(d,g),n._.dirty=1,!l.fy)break;d="y",g=l.y;case"y":l.fy&&(g=-l.y-(l.height||0));case"ry":if("ry"==d&&"rect"==n.type)break;case"cy":u.setAttribute(d,g),n.pattern&&y(n),n._.dirty=1;break;case"r":"rect"==n.type?x(u,{rx:g,ry:g}):u.setAttribute(d,g),n._.dirty=1;break;case"src":"image"==n.type&&u.setAttributeNS(p,"href",g);break;case"stroke-width":(1!=n._.sx||1!=n._.sy)&&(g/=s(o(n._.sx),o(n._.sy))||1),n.paper._vbSize&&(g*=n.paper._vbSize),u.setAttribute(d,g),l["stroke-dasharray"]&&_(n,l["stroke-dasharray"],a),n._.arrows&&("startString"in n._.arrows&&m(n,n._.arrows.startString),"endString"in n._.arrows&&m(n,n._.arrows.endString,1));break;case"stroke-dasharray":_(n,g,a);break;case"fill":var M=r(g).match(t._ISURL);if(M){B=x("pattern");var F=x("image");B.id=t.createUUID(),x(B,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1}),x(F,{x:0,y:0,"xlink:href":M[1]}),B.appendChild(F),function(e){t._preload(M[1],function(){var t=this.offsetWidth,r=this.offsetHeight;x(e,{width:t,height:r}),x(F,{width:t,height:r}),n.paper.safari()})}(B),n.paper.defs.appendChild(B),x(u,{fill:"url(#"+B.id+")"}),n.pattern=B,n.pattern&&y(n);break}var L=t.getRGB(g);if(L.error){if(("circle"==n.type||"ellipse"==n.type||"r"!=r(g).charAt())&&v(n,g)){if("opacity"in l||"fill-opacity"in l){var N=t._g.doc.getElementById(u.getAttribute("fill").replace(/^url\(#|\)$/g,c));if(N){var P=N.getElementsByTagName("stop");x(P[P.length-1],{"stop-opacity":("opacity"in l?l.opacity:1)*("fill-opacity"in l?l["fill-opacity"]:1)})}}l.gradient=g,l.fill="none";break}}else delete a.gradient,delete l.gradient,!t.is(l.opacity,"undefined")&&t.is(a.opacity,"undefined")&&x(u,{opacity:l.opacity}),!t.is(l["fill-opacity"],"undefined")&&t.is(a["fill-opacity"],"undefined")&&x(u,{"fill-opacity":l["fill-opacity"]});L[e]("opacity")&&x(u,{"fill-opacity":L.opacity>1?L.opacity/100:L.opacity});case"stroke":L=t.getRGB(g),u.setAttribute(d,L.hex),"stroke"==d&&L[e]("opacity")&&x(u,{"stroke-opacity":L.opacity>1?L.opacity/100:L.opacity}),"stroke"==d&&n._.arrows&&("startString"in n._.arrows&&m(n,n._.arrows.startString),"endString"in n._.arrows&&m(n,n._.arrows.endString,1));break;case"gradient":("circle"==n.type||"ellipse"==n.type||"r"!=r(g).charAt())&&v(n,g);break;case"opacity":l.gradient&&!l[e]("stroke-opacity")&&x(u,{"stroke-opacity":g>1?g/100:g});case"fill-opacity":if(l.gradient){N=t._g.doc.getElementById(u.getAttribute("fill").replace(/^url\(#|\)$/g,c)),N&&(P=N.getElementsByTagName("stop"),x(P[P.length-1],{"stop-opacity":g}));break}default:"font-size"==d&&(g=i(g,10)+"px");var E=d.replace(/(\-.)/g,function(t){return t.substring(1).toUpperCase()});u.style[E]=g,n._.dirty=1,u.setAttribute(d,g)}}C(n,a),u.style.visibility=f},k=1.2,C=function(n,a){if("text"==n.type&&(a[e]("text")||a[e]("font")||a[e]("font-size")||a[e]("x")||a[e]("y"))){var s=n.attrs,o=n.node,u=o.firstChild?i(t._g.doc.defaultView.getComputedStyle(o.firstChild,c).getPropertyValue("font-size"),10):10;if(a[e]("text")){for(s.text=a.text;o.firstChild;)o.removeChild(o.firstChild);for(var h,l=r(a.text).split("\n"),f=[],p=0,d=l.length;d>p;p++)h=x("tspan"),p&&x(h,{dy:u*k,x:s.x}),h.appendChild(t._g.doc.createTextNode(l[p])),o.appendChild(h),f[p]=h}else for(f=o.getElementsByTagName("tspan"),p=0,d=f.length;d>p;p++)p?x(f[p],{dy:u*k,x:s.x}):x(f[0],{dy:0});x(o,{x:s.x,y:s.y}),n._.dirty=1;var g=n._getBBox(),v=s.y-(g.y+g.height/2);v&&t.is(v,"finite")&&x(f[0],{dy:v})}},B=function(e,r){this[0]=this.node=e,e.raphael=!0,this.id=t._oid++,e.raphaelid=this.id,this.matrix=t.matrix(),this.realPath=null,this.paper=r,this.attrs=this.attrs||{},this._={transform:[],sx:1,sy:1,deg:0,dx:0,dy:0,dirty:1},!r.bottom&&(r.bottom=this),this.prev=r.top,r.top&&(r.top.next=this),r.top=this,this.next=null},S=t.el;B.prototype=S,S.constructor=B,t._engine.path=function(t,e){var r=x("path");e.canvas&&e.canvas.appendChild(r);var n=new B(r,e);return n.type="path",w(n,{fill:"none",stroke:"#000",path:t}),n},S.rotate=function(t,e,i){if(this.removed)return this;if(t=r(t).split(h),t.length-1&&(e=n(t[1]),i=n(t[2])),t=n(t[0]),null==i&&(e=i),null==e||null==i){var a=this.getBBox(1);e=a.x+a.width/2,i=a.y+a.height/2}return this.transform(this._.transform.concat([["r",t,e,i]])),this},S.scale=function(t,e,i,a){if(this.removed)return this;if(t=r(t).split(h),t.length-1&&(e=n(t[1]),i=n(t[2]),a=n(t[3])),t=n(t[0]),null==e&&(e=t),null==a&&(i=a),null==i||null==a)var s=this.getBBox(1);return i=null==i?s.x+s.width/2:i,a=null==a?s.y+s.height/2:a,this.transform(this._.transform.concat([["s",t,e,i,a]])),this},S.translate=function(t,e){return this.removed?this:(t=r(t).split(h),t.length-1&&(e=n(t[1])),t=n(t[0])||0,e=+e||0,this.transform(this._.transform.concat([["t",t,e]])),this)},S.transform=function(r){var n=this._;if(null==r)return n.transform;if(t._extractTransform(this,r),this.clip&&x(this.clip,{transform:this.matrix.invert()}),this.pattern&&y(this),this.node&&x(this.node,{transform:this.matrix}),1!=n.sx||1!=n.sy){var i=this.attrs[e]("stroke-width")?this.attrs["stroke-width"]:1;this.attr({"stroke-width":i})}return this},S.hide=function(){return!this.removed&&this.paper.safari(this.node.style.display="none"),this},S.show=function(){return!this.removed&&this.paper.safari(this.node.style.display=""),this},S.remove=function(){if(!this.removed&&this.node.parentNode){var e=this.paper;e.__set__&&e.__set__.exclude(this),l.unbind("raphael.*.*."+this.id),this.gradient&&e.defs.removeChild(this.gradient),t._tear(this,e),"a"==this.node.parentNode.tagName.toLowerCase()?this.node.parentNode.parentNode.removeChild(this.node.parentNode):this.node.parentNode.removeChild(this.node);for(var r in this)this[r]="function"==typeof this[r]?t._removedFactory(r):null;this.removed=!0}},S._getBBox=function(){if("none"==this.node.style.display){this.show();var t=!0}var e={};try{e=this.node.getBBox()}catch(r){}finally{e=e||{}}return t&&this.hide(),e},S.attr=function(r,n){if(this.removed)return this;if(null==r){var i={};for(var a in this.attrs)this.attrs[e](a)&&(i[a]=this.attrs[a]);return i.gradient&&"none"==i.fill&&(i.fill=i.gradient)&&delete i.gradient,i.transform=this._.transform,i}if(null==n&&t.is(r,"string")){if("fill"==r&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;if("transform"==r)return this._.transform;for(var s=r.split(h),o={},u=0,c=s.length;c>u;u++)r=s[u],o[r]=r in this.attrs?this.attrs[r]:t.is(this.paper.customAttributes[r],"function")?this.paper.customAttributes[r].def:t._availableAttrs[r];return c-1?o:o[s[0]]}if(null==n&&t.is(r,"array")){for(o={},u=0,c=r.length;c>u;u++)o[r[u]]=this.attr(r[u]);return o}if(null!=n){var f={};f[r]=n}else null!=r&&t.is(r,"object")&&(f=r);for(var p in f)l("raphael.attr."+p+"."+this.id,this,f[p]);for(p in this.paper.customAttributes)if(this.paper.customAttributes[e](p)&&f[e](p)&&t.is(this.paper.customAttributes[p],"function")){var d=this.paper.customAttributes[p].apply(this,[].concat(f[p]));this.attrs[p]=f[p];for(var g in d)d[e](g)&&(f[g]=d[g])}return w(this,f),this},S.toFront=function(){if(this.removed)return this;"a"==this.node.parentNode.tagName.toLowerCase()?this.node.parentNode.parentNode.appendChild(this.node.parentNode):this.node.parentNode.appendChild(this.node);var e=this.paper;return e.top!=this&&t._tofront(this,e),this},S.toBack=function(){if(this.removed)return this;var e=this.node.parentNode;return"a"==e.tagName.toLowerCase()?e.parentNode.insertBefore(this.node.parentNode,this.node.parentNode.parentNode.firstChild):e.firstChild!=this.node&&e.insertBefore(this.node,this.node.parentNode.firstChild),t._toback(this,this.paper),this.paper,this},S.insertAfter=function(e){if(this.removed)return this;var r=e.node||e[e.length-1].node;return r.nextSibling?r.parentNode.insertBefore(this.node,r.nextSibling):r.parentNode.appendChild(this.node),t._insertafter(this,e,this.paper),this},S.insertBefore=function(e){if(this.removed)return this;var r=e.node||e[0].node;return r.parentNode.insertBefore(this.node,r),t._insertbefore(this,e,this.paper),this},S.blur=function(e){var r=this;if(0!==+e){var n=x("filter"),i=x("feGaussianBlur");r.attrs.blur=e,n.id=t.createUUID(),x(i,{stdDeviation:+e||1.5}),n.appendChild(i),r.paper.defs.appendChild(n),r._blur=n,x(r.node,{filter:"url(#"+n.id+")"})}else r._blur&&(r._blur.parentNode.removeChild(r._blur),delete r._blur,delete r.attrs.blur),r.node.removeAttribute("filter")},t._engine.circle=function(t,e,r,n){var i=x("circle");t.canvas&&t.canvas.appendChild(i);var a=new B(i,t);return a.attrs={cx:e,cy:r,r:n,fill:"none",stroke:"#000"},a.type="circle",x(i,a.attrs),a},t._engine.rect=function(t,e,r,n,i,a){var s=x("rect");t.canvas&&t.canvas.appendChild(s);var o=new B(s,t);return o.attrs={x:e,y:r,width:n,height:i,r:a||0,rx:a||0,ry:a||0,fill:"none",stroke:"#000"},o.type="rect",x(s,o.attrs),o},t._engine.ellipse=function(t,e,r,n,i){var a=x("ellipse");t.canvas&&t.canvas.appendChild(a);var s=new B(a,t);return s.attrs={cx:e,cy:r,rx:n,ry:i,fill:"none",stroke:"#000"},s.type="ellipse",x(a,s.attrs),s},t._engine.image=function(t,e,r,n,i,a){var s=x("image");x(s,{x:r,y:n,width:i,height:a,preserveAspectRatio:"none"}),s.setAttributeNS(p,"href",e),t.canvas&&t.canvas.appendChild(s);var o=new B(s,t);return o.attrs={x:r,y:n,width:i,height:a,src:e},o.type="image",o},t._engine.text=function(e,r,n,i){var a=x("text");e.canvas&&e.canvas.appendChild(a);var s=new B(a,e);return s.attrs={x:r,y:n,"text-anchor":"middle",text:i,font:t._availableAttrs.font,stroke:"none",fill:"#000"},s.type="text",w(s,s.attrs),s},t._engine.setSize=function(t,e){return this.width=t||this.width,this.height=e||this.height,this.canvas.setAttribute("width",this.width),this.canvas.setAttribute("height",this.height),this._viewBox&&this.setViewBox.apply(this,this._viewBox),this},t._engine.create=function(){var e=t._getContainer.apply(0,arguments),r=e&&e.container,n=e.x,i=e.y,a=e.width,s=e.height;if(!r)throw Error("SVG container not found.");var o,u=x("svg"),h="overflow:hidden;";return n=n||0,i=i||0,a=a||512,s=s||342,x(u,{height:s,version:1.1,width:a,xmlns:"http://www.w3.org/2000/svg"}),1==r?(u.style.cssText=h+"position:absolute;left:"+n+"px;top:"+i+"px",t._g.doc.body.appendChild(u),o=1):(u.style.cssText=h+"position:relative",r.firstChild?r.insertBefore(u,r.firstChild):r.appendChild(u)),r=new t._Paper,r.width=a,r.height=s,r.canvas=u,r.clear(),r._left=r._top=0,o&&(r.renderfix=function(){}),r.renderfix(),r},t._engine.setViewBox=function(t,e,r,n,i){l("raphael.setViewBox",this,this._viewBox,[t,e,r,n,i]);var a,o,u=s(r/this.width,n/this.height),h=this.top,c=i?"meet":"xMinYMin";for(null==t?(this._vbSize&&(u=1),delete this._vbSize,a="0 0 "+this.width+f+this.height):(this._vbSize=u,a=t+f+e+f+r+f+n),x(this.canvas,{viewBox:a,preserveAspectRatio:c});u&&h;)o="stroke-width"in h.attrs?h.attrs["stroke-width"]:1,h.attr({"stroke-width":o}),h._.dirty=1,h._.dirtyT=1,h=h.prev;return this._viewBox=[t,e,r,n,!!i],this},t.prototype.renderfix=function(){var t,e=this.canvas,r=e.style;try{t=e.getScreenCTM()||e.createSVGMatrix()}catch(n){t=e.createSVGMatrix()}var i=-t.e%1,a=-t.f%1;(i||a)&&(i&&(this._left=(this._left+i)%1,r.left=this._left+"px"),a&&(this._top=(this._top+a)%1,r.top=this._top+"px"))},t.prototype.clear=function(){t.eve("raphael.clear",this);for(var e=this.canvas;e.firstChild;)e.removeChild(e.firstChild);this.bottom=this.top=null,(this.desc=x("desc")).appendChild(t._g.doc.createTextNode("Created with Raphaël "+t.version)),e.appendChild(this.desc),e.appendChild(this.defs=x("defs"))},t.prototype.remove=function(){l("raphael.remove",this),this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var e in this)this[e]="function"==typeof this[e]?t._removedFactory(e):null};var A=t.st;for(var T in S)S[e](T)&&!A[e](T)&&(A[T]=function(t){return function(){var e=arguments;return this.forEach(function(r){r[t].apply(r,e)})}}(T))}});(function(t,e){"function"==typeof define&&define.amd?require(["raphael"],e):t.Raphael&&e(t.Raphael)})(this,function(t){if(t.vml){var e="hasOwnProperty",r=String,i=parseFloat,n=Math,a=n.round,s=n.max,o=n.min,l=n.abs,h="fill",u=/[, ]+/,c=t.eve,f=" progid:DXImageTransform.Microsoft",p=" ",d="",g={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},x=/([clmz]),?([^clmz]*)/gi,v=/ progid:\S+Blur\([^\)]+\)/g,y=/-?[^,\s-]+/g,m="position:absolute;left:0;top:0;width:1px;height:1px",b=21600,_={path:1,rect:1,image:1},w={circle:1,ellipse:1},k=function(e){var i=/[ahqstv]/gi,n=t._pathToAbsolute;if(r(e).match(i)&&(n=t._path2curve),i=/[clmz]/g,n==t._pathToAbsolute&&!r(e).match(i)){var s=r(e).replace(x,function(t,e,r){var i=[],n="m"==e.toLowerCase(),s=g[e];return r.replace(y,function(t){n&&2==i.length&&(s+=i+g["m"==e?"l":"L"],i=[]),i.push(a(t*b))}),s+i});return s}var o,l,h=n(e);s=[];for(var u=0,c=h.length;c>u;u++){o=h[u],l=h[u][0].toLowerCase(),"z"==l&&(l="x");for(var f=1,v=o.length;v>f;f++)l+=a(o[f]*b)+(f!=v-1?",":d);s.push(l)}return s.join(p)},C=function(e,r,i){var n=t.matrix();return n.rotate(-e,.5,.5),{dx:n.x(r,i),dy:n.y(r,i)}},B=function(t,e,r,i,n,a){var s=t._,o=t.matrix,u=s.fillpos,c=t.node,f=c.style,d=1,g="",x=b/e,v=b/r;if(f.visibility="hidden",e&&r){if(c.coordsize=l(x)+p+l(v),f.rotation=a*(0>e*r?-1:1),a){var y=C(a,i,n);i=y.dx,n=y.dy}if(0>e&&(g+="x"),0>r&&(g+=" y")&&(d=-1),f.flip=g,c.coordorigin=i*-x+p+n*-v,u||s.fillsize){var m=c.getElementsByTagName(h);m=m&&m[0],c.removeChild(m),u&&(y=C(a,o.x(u[0],u[1]),o.y(u[0],u[1])),m.position=y.dx*d+p+y.dy*d),s.fillsize&&(m.size=s.fillsize[0]*l(e)+p+s.fillsize[1]*l(r)),c.appendChild(m)}f.visibility="visible"}};t.toString=function(){return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël "+this.version};var S=function(t,e,i){for(var n=r(e).toLowerCase().split("-"),a=i?"end":"start",s=n.length,o="classic",l="medium",h="medium";s--;)switch(n[s]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":o=n[s];break;case"wide":case"narrow":h=n[s];break;case"long":case"short":l=n[s]}var u=t.node.getElementsByTagName("stroke")[0];u[a+"arrow"]=o,u[a+"arrowlength"]=l,u[a+"arrowwidth"]=h},A=function(n,l){n.attrs=n.attrs||{};var c=n.node,f=n.attrs,g=c.style,x=_[n.type]&&(l.x!=f.x||l.y!=f.y||l.width!=f.width||l.height!=f.height||l.cx!=f.cx||l.cy!=f.cy||l.rx!=f.rx||l.ry!=f.ry||l.r!=f.r),v=w[n.type]&&(f.cx!=l.cx||f.cy!=l.cy||f.r!=l.r||f.rx!=l.rx||f.ry!=l.ry),y=n;for(var m in l)l[e](m)&&(f[m]=l[m]);if(x&&(f.path=t._getPath[n.type](n),n._.dirty=1),l.href&&(c.href=l.href),l.title&&(c.title=l.title),l.target&&(c.target=l.target),l.cursor&&(g.cursor=l.cursor),"blur"in l&&n.blur(l.blur),(l.path&&"path"==n.type||x)&&(c.path=k(~r(f.path).toLowerCase().indexOf("r")?t._pathToAbsolute(f.path):f.path),"image"==n.type&&(n._.fillpos=[f.x,f.y],n._.fillsize=[f.width,f.height],B(n,1,1,0,0,0))),"transform"in l&&n.transform(l.transform),v){var C=+f.cx,A=+f.cy,N=+f.rx||+f.r||0,E=+f.ry||+f.r||0;c.path=t.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x",a((C-N)*b),a((A-E)*b),a((C+N)*b),a((A+E)*b),a(C*b))}if("clip-rect"in l){var M=r(l["clip-rect"]).split(u);if(4==M.length){M[2]=+M[2]+ +M[0],M[3]=+M[3]+ +M[1];var z=c.clipRect||t._g.doc.createElement("div"),F=z.style;F.clip=t.format("rect({1}px {2}px {3}px {0}px)",M),c.clipRect||(F.position="absolute",F.top=0,F.left=0,F.width=n.paper.width+"px",F.height=n.paper.height+"px",c.parentNode.insertBefore(z,c),z.appendChild(c),c.clipRect=z)}l["clip-rect"]||c.clipRect&&(c.clipRect.style.clip="auto")}if(n.textpath){var R=n.textpath.style;l.font&&(R.font=l.font),l["font-family"]&&(R.fontFamily='"'+l["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g,d)+'"'),l["font-size"]&&(R.fontSize=l["font-size"]),l["font-weight"]&&(R.fontWeight=l["font-weight"]),l["font-style"]&&(R.fontStyle=l["font-style"])}if("arrow-start"in l&&S(y,l["arrow-start"]),"arrow-end"in l&&S(y,l["arrow-end"],1),null!=l.opacity||null!=l["stroke-width"]||null!=l.fill||null!=l.src||null!=l.stroke||null!=l["stroke-width"]||null!=l["stroke-opacity"]||null!=l["fill-opacity"]||null!=l["stroke-dasharray"]||null!=l["stroke-miterlimit"]||null!=l["stroke-linejoin"]||null!=l["stroke-linecap"]){var P=c.getElementsByTagName(h),I=!1;if(P=P&&P[0],!P&&(I=P=L(h)),"image"==n.type&&l.src&&(P.src=l.src),l.fill&&(P.on=!0),(null==P.on||"none"==l.fill||null===l.fill)&&(P.on=!1),P.on&&l.fill){var j=r(l.fill).match(t._ISURL);if(j){P.parentNode==c&&c.removeChild(P),P.rotate=!0,P.src=j[1],P.type="tile";var q=n.getBBox(1);P.position=q.x+p+q.y,n._.fillpos=[q.x,q.y],t._preload(j[1],function(){n._.fillsize=[this.offsetWidth,this.offsetHeight]})}else P.color=t.getRGB(l.fill).hex,P.src=d,P.type="solid",t.getRGB(l.fill).error&&(y.type in{circle:1,ellipse:1}||"r"!=r(l.fill).charAt())&&T(y,l.fill,P)&&(f.fill="none",f.gradient=l.fill,P.rotate=!1)}if("fill-opacity"in l||"opacity"in l){var D=((+f["fill-opacity"]+1||2)-1)*((+f.opacity+1||2)-1)*((+t.getRGB(l.fill).o+1||2)-1);D=o(s(D,0),1),P.opacity=D,P.src&&(P.color="none")}c.appendChild(P);var O=c.getElementsByTagName("stroke")&&c.getElementsByTagName("stroke")[0],V=!1;!O&&(V=O=L("stroke")),(l.stroke&&"none"!=l.stroke||l["stroke-width"]||null!=l["stroke-opacity"]||l["stroke-dasharray"]||l["stroke-miterlimit"]||l["stroke-linejoin"]||l["stroke-linecap"])&&(O.on=!0),("none"==l.stroke||null===l.stroke||null==O.on||0==l.stroke||0==l["stroke-width"])&&(O.on=!1);var Y=t.getRGB(l.stroke);O.on&&l.stroke&&(O.color=Y.hex),D=((+f["stroke-opacity"]+1||2)-1)*((+f.opacity+1||2)-1)*((+Y.o+1||2)-1);var G=.75*(i(l["stroke-width"])||1);if(D=o(s(D,0),1),null==l["stroke-width"]&&(G=f["stroke-width"]),l["stroke-width"]&&(O.weight=G),G&&1>G&&(D*=G)&&(O.weight=1),O.opacity=D,l["stroke-linejoin"]&&(O.joinstyle=l["stroke-linejoin"]||"miter"),O.miterlimit=l["stroke-miterlimit"]||8,l["stroke-linecap"]&&(O.endcap="butt"==l["stroke-linecap"]?"flat":"square"==l["stroke-linecap"]?"square":"round"),l["stroke-dasharray"]){var W={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};O.dashstyle=W[e](l["stroke-dasharray"])?W[l["stroke-dasharray"]]:d}V&&c.appendChild(O)}if("text"==y.type){y.paper.canvas.style.display=d;var X=y.paper.span,H=100,U=f.font&&f.font.match(/\d+(?:\.\d*)?(?=px)/);g=X.style,f.font&&(g.font=f.font),f["font-family"]&&(g.fontFamily=f["font-family"]),f["font-weight"]&&(g.fontWeight=f["font-weight"]),f["font-style"]&&(g.fontStyle=f["font-style"]),U=i(f["font-size"]||U&&U[0])||10,g.fontSize=U*H+"px",y.textpath.string&&(X.innerHTML=r(y.textpath.string).replace(/"));var $=X.getBoundingClientRect();y.W=f.w=($.right-$.left)/H,y.H=f.h=($.bottom-$.top)/H,y.X=f.x,y.Y=f.y+y.H/2,("x"in l||"y"in l)&&(y.path.v=t.format("m{0},{1}l{2},{1}",a(f.x*b),a(f.y*b),a(f.x*b)+1));for(var Z=["x","y","text","font","font-family","font-weight","font-style","font-size"],Q=0,J=Z.length;J>Q;Q++)if(Z[Q]in l){y._.dirty=1;break}switch(f["text-anchor"]){case"start":y.textpath.style["v-text-align"]="left",y.bbx=y.W/2;break;case"end":y.textpath.style["v-text-align"]="right",y.bbx=-y.W/2;break;default:y.textpath.style["v-text-align"]="center",y.bbx=0}y.textpath.style["v-text-kern"]=!0}},T=function(e,a,s){e.attrs=e.attrs||{};var o=(e.attrs,Math.pow),l="linear",h=".5 .5";if(e.attrs.gradient=a,a=r(a).replace(t._radial_gradient,function(t,e,r){return l="radial",e&&r&&(e=i(e),r=i(r),o(e-.5,2)+o(r-.5,2)>.25&&(r=n.sqrt(.25-o(e-.5,2))*(2*(r>.5)-1)+.5),h=e+p+r),d}),a=a.split(/\s*\-\s*/),"linear"==l){var u=a.shift();if(u=-i(u),isNaN(u))return null}var c=t._parseDots(a);if(!c)return null;if(e=e.shape||e.node,c.length){e.removeChild(s),s.on=!0,s.method="none",s.color=c[0].color,s.color2=c[c.length-1].color;for(var f=[],g=0,x=c.length;x>g;g++)c[g].offset&&f.push(c[g].offset+p+c[g].color);s.colors=f.length?f.join():"0% "+s.color,"radial"==l?(s.type="gradientTitle",s.focus="100%",s.focussize="0 0",s.focusposition=h,s.angle=0):(s.type="gradient",s.angle=(270-u)%360),e.appendChild(s)}return 1},N=function(e,r){this[0]=this.node=e,e.raphael=!0,this.id=t._oid++,e.raphaelid=this.id,this.X=0,this.Y=0,this.attrs={},this.paper=r,this.matrix=t.matrix(),this._={transform:[],sx:1,sy:1,dx:0,dy:0,deg:0,dirty:1,dirtyT:1},!r.bottom&&(r.bottom=this),this.prev=r.top,r.top&&(r.top.next=this),r.top=this,this.next=null},E=t.el;N.prototype=E,E.constructor=N,E.transform=function(e){if(null==e)return this._.transform;var i,n=this.paper._viewBoxShift,a=n?"s"+[n.scale,n.scale]+"-1-1t"+[n.dx,n.dy]:d;n&&(i=e=r(e).replace(/\.{3}|\u2026/g,this._.transform||d)),t._extractTransform(this,a+e);var s,o=this.matrix.clone(),l=this.skew,h=this.node,u=~r(this.attrs.fill).indexOf("-"),c=!r(this.attrs.fill).indexOf("url(");if(o.translate(-.5,-.5),c||u||"image"==this.type)if(l.matrix="1 0 0 1",l.offset="0 0",s=o.split(),u&&s.noRotation||!s.isSimple){h.style.filter=o.toFilter();var f=this.getBBox(),g=this.getBBox(1),x=f.x-g.x,v=f.y-g.y;h.coordorigin=x*-b+p+v*-b,B(this,1,1,x,v,0)}else h.style.filter=d,B(this,s.scalex,s.scaley,s.dx,s.dy,s.rotate);else h.style.filter=d,l.matrix=r(o),l.offset=o.offset();return i&&(this._.transform=i),this},E.rotate=function(t,e,n){if(this.removed)return this;if(null!=t){if(t=r(t).split(u),t.length-1&&(e=i(t[1]),n=i(t[2])),t=i(t[0]),null==n&&(e=n),null==e||null==n){var a=this.getBBox(1);e=a.x+a.width/2,n=a.y+a.height/2}return this._.dirtyT=1,this.transform(this._.transform.concat([["r",t,e,n]])),this}},E.translate=function(t,e){return this.removed?this:(t=r(t).split(u),t.length-1&&(e=i(t[1])),t=i(t[0])||0,e=+e||0,this._.bbox&&(this._.bbox.x+=t,this._.bbox.y+=e),this.transform(this._.transform.concat([["t",t,e]])),this)},E.scale=function(t,e,n,a){if(this.removed)return this;if(t=r(t).split(u),t.length-1&&(e=i(t[1]),n=i(t[2]),a=i(t[3]),isNaN(n)&&(n=null),isNaN(a)&&(a=null)),t=i(t[0]),null==e&&(e=t),null==a&&(n=a),null==n||null==a)var s=this.getBBox(1);return n=null==n?s.x+s.width/2:n,a=null==a?s.y+s.height/2:a,this.transform(this._.transform.concat([["s",t,e,n,a]])),this._.dirtyT=1,this},E.hide=function(){return!this.removed&&(this.node.style.display="none"),this},E.show=function(){return!this.removed&&(this.node.style.display=d),this},E._getBBox=function(){return this.removed?{}:{x:this.X+(this.bbx||0)-this.W/2,y:this.Y-this.H,width:this.W,height:this.H}},E.remove=function(){if(!this.removed&&this.node.parentNode){this.paper.__set__&&this.paper.__set__.exclude(this),t.eve.unbind("raphael.*.*."+this.id),t._tear(this,this.paper),this.node.parentNode.removeChild(this.node),this.shape&&this.shape.parentNode.removeChild(this.shape);for(var e in this)this[e]="function"==typeof this[e]?t._removedFactory(e):null;this.removed=!0}},E.attr=function(r,i){if(this.removed)return this;if(null==r){var n={};for(var a in this.attrs)this.attrs[e](a)&&(n[a]=this.attrs[a]);return n.gradient&&"none"==n.fill&&(n.fill=n.gradient)&&delete n.gradient,n.transform=this._.transform,n}if(null==i&&t.is(r,"string")){if(r==h&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;for(var s=r.split(u),o={},l=0,f=s.length;f>l;l++)r=s[l],o[r]=r in this.attrs?this.attrs[r]:t.is(this.paper.customAttributes[r],"function")?this.paper.customAttributes[r].def:t._availableAttrs[r];return f-1?o:o[s[0]]}if(this.attrs&&null==i&&t.is(r,"array")){for(o={},l=0,f=r.length;f>l;l++)o[r[l]]=this.attr(r[l]);return o}var p;null!=i&&(p={},p[r]=i),null==i&&t.is(r,"object")&&(p=r);for(var d in p)c("raphael.attr."+d+"."+this.id,this,p[d]);if(p){for(d in this.paper.customAttributes)if(this.paper.customAttributes[e](d)&&p[e](d)&&t.is(this.paper.customAttributes[d],"function")){var g=this.paper.customAttributes[d].apply(this,[].concat(p[d]));this.attrs[d]=p[d];for(var x in g)g[e](x)&&(p[x]=g[x])}p.text&&"text"==this.type&&(this.textpath.string=p.text),A(this,p)}return this},E.toFront=function(){return!this.removed&&this.node.parentNode.appendChild(this.node),this.paper&&this.paper.top!=this&&t._tofront(this,this.paper),this},E.toBack=function(){return this.removed?this:(this.node.parentNode.firstChild!=this.node&&(this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild),t._toback(this,this.paper)),this)},E.insertAfter=function(e){return this.removed?this:(e.constructor==t.st.constructor&&(e=e[e.length-1]),e.node.nextSibling?e.node.parentNode.insertBefore(this.node,e.node.nextSibling):e.node.parentNode.appendChild(this.node),t._insertafter(this,e,this.paper),this)},E.insertBefore=function(e){return this.removed?this:(e.constructor==t.st.constructor&&(e=e[0]),e.node.parentNode.insertBefore(this.node,e.node),t._insertbefore(this,e,this.paper),this)},E.blur=function(e){var r=this.node.runtimeStyle,i=r.filter;i=i.replace(v,d),0!==+e?(this.attrs.blur=e,r.filter=i+p+f+".Blur(pixelradius="+(+e||1.5)+")",r.margin=t.format("-{0}px 0 0 -{0}px",a(+e||1.5))):(r.filter=i,r.margin=0,delete this.attrs.blur)},t._engine.path=function(t,e){var r=L("shape");r.style.cssText=m,r.coordsize=b+p+b,r.coordorigin=e.coordorigin;var i=new N(r,e),n={fill:"none",stroke:"#000"};t&&(n.path=t),i.type="path",i.path=[],i.Path=d,A(i,n),e.canvas.appendChild(r);var a=L("skew");return a.on=!0,r.appendChild(a),i.skew=a,i.transform(d),i},t._engine.rect=function(e,r,i,n,a,s){var o=t._rectPath(r,i,n,a,s),l=e.path(o),h=l.attrs;return l.X=h.x=r,l.Y=h.y=i,l.W=h.width=n,l.H=h.height=a,h.r=s,h.path=o,l.type="rect",l},t._engine.ellipse=function(t,e,r,i,n){var a=t.path();return a.attrs,a.X=e-i,a.Y=r-n,a.W=2*i,a.H=2*n,a.type="ellipse",A(a,{cx:e,cy:r,rx:i,ry:n}),a},t._engine.circle=function(t,e,r,i){var n=t.path();return n.attrs,n.X=e-i,n.Y=r-i,n.W=n.H=2*i,n.type="circle",A(n,{cx:e,cy:r,r:i}),n},t._engine.image=function(e,r,i,n,a,s){var o=t._rectPath(i,n,a,s),l=e.path(o).attr({stroke:"none"}),u=l.attrs,c=l.node,f=c.getElementsByTagName(h)[0];return u.src=r,l.X=u.x=i,l.Y=u.y=n,l.W=u.width=a,l.H=u.height=s,u.path=o,l.type="image",f.parentNode==c&&c.removeChild(f),f.rotate=!0,f.src=r,f.type="tile",l._.fillpos=[i,n],l._.fillsize=[a,s],c.appendChild(f),B(l,1,1,0,0,0),l},t._engine.text=function(e,i,n,s){var o=L("shape"),l=L("path"),h=L("textpath");i=i||0,n=n||0,s=s||"",l.v=t.format("m{0},{1}l{2},{1}",a(i*b),a(n*b),a(i*b)+1),l.textpathok=!0,h.string=r(s),h.on=!0,o.style.cssText=m,o.coordsize=b+p+b,o.coordorigin="0 0";var u=new N(o,e),c={fill:"#000",stroke:"none",font:t._availableAttrs.font,text:s};u.shape=o,u.path=l,u.textpath=h,u.type="text",u.attrs.text=r(s),u.attrs.x=i,u.attrs.y=n,u.attrs.w=1,u.attrs.h=1,A(u,c),o.appendChild(h),o.appendChild(l),e.canvas.appendChild(o);var f=L("skew");return f.on=!0,o.appendChild(f),u.skew=f,u.transform(d),u},t._engine.setSize=function(e,r){var i=this.canvas.style;return this.width=e,this.height=r,e==+e&&(e+="px"),r==+r&&(r+="px"),i.width=e,i.height=r,i.clip="rect(0 "+e+" "+r+" 0)",this._viewBox&&t._engine.setViewBox.apply(this,this._viewBox),this},t._engine.setViewBox=function(e,r,i,n,a){t.eve("raphael.setViewBox",this,this._viewBox,[e,r,i,n,a]);var o,l,h=this.width,u=this.height,c=1/s(i/h,n/u);return a&&(o=u/n,l=h/i,h>i*o&&(e-=(h-i*o)/2/o),u>n*l&&(r-=(u-n*l)/2/l)),this._viewBox=[e,r,i,n,!!a],this._viewBoxShift={dx:-e,dy:-r,scale:c},this.forEach(function(t){t.transform("...")}),this};var L;t._engine.initWin=function(t){var e=t.document;e.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)");try{!e.namespaces.rvml&&e.namespaces.add("rvml","urn:schemas-microsoft-com:vml"),L=function(t){return e.createElement("')}}catch(r){L=function(t){return e.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},t._engine.initWin(t._g.win),t._engine.create=function(){var e=t._getContainer.apply(0,arguments),r=e.container,i=e.height,n=e.width,a=e.x,s=e.y;if(!r)throw Error("VML container not found.");var o=new t._Paper,l=o.canvas=t._g.doc.createElement("div"),h=l.style;return a=a||0,s=s||0,n=n||512,i=i||342,o.width=n,o.height=i,n==+n&&(n+="px"),i==+i&&(i+="px"),o.coordsize=1e3*b+p+1e3*b,o.coordorigin="0 0",o.span=t._g.doc.createElement("span"),o.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",l.appendChild(o.span),h.cssText=t.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",n,i),1==r?(t._g.doc.body.appendChild(l),h.left=a+"px",h.top=s+"px",h.position="absolute"):r.firstChild?r.insertBefore(l,r.firstChild):r.appendChild(l),o.renderfix=function(){},o},t.prototype.clear=function(){t.eve("raphael.clear",this),this.canvas.innerHTML=d,this.span=t._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},t.prototype.remove=function(){t.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var e in this)this[e]="function"==typeof this[e]?t._removedFactory(e):null;return!0};var M=t.st;for(var z in E)E[e](z)&&!M[e](z)&&(M[z]=function(t){return function(){var e=arguments;return this.forEach(function(r){r[t].apply(r,e)})}}(z))}}); \ No newline at end of file diff --git a/tools/pythonpkg/duckdb_query_graph/treant.js b/tools/pythonpkg/duckdb_query_graph/treant.js deleted file mode 100644 index fe300baa1edc..000000000000 --- a/tools/pythonpkg/duckdb_query_graph/treant.js +++ /dev/null @@ -1,1330 +0,0 @@ -/* -* Treant.js -* -* (c) 2013 Fran Peručić -* -* Treant is an open-source JavaScipt library for visualization of tree diagrams. -* It implements the node positioning algorithm of John Q. Walker II "Positioning nodes for General Trees". -* -* References: -* Emilio Cortegoso Lobato: ECOTree.js v1.0 (October 26th, 2006) -* -*/ - -;(function(){ - - var UTIL = { - inheritAttrs: function(me, from) { - for (var attr in from) { - if(typeof from[attr] !== 'function') { - if(me[attr] instanceof Object && from[attr] instanceof Object) { - this.inheritAttrs(me[attr], from[attr]); - } else { - me[attr] = from[attr]; - } - } - } - }, - - createMerge: function(obj1, obj2) { - var newObj = {}; - if(obj1) this.inheritAttrs(newObj, this.cloneObj(obj1)); - if(obj2) this.inheritAttrs(newObj, obj2); - return newObj; - }, - - cloneObj: function (obj) { - if (Object(obj) !== obj) { - return obj; - } - var res = new obj.constructor(); - for (var key in obj) if (obj["hasOwnProperty"](key)) { - res[key] = this.cloneObj(obj[key]); - } - return res; - }, - addEvent: function(el, eventType, handler) { - if (el.addEventListener) { // DOM Level 2 browsers - el.addEventListener(eventType, handler, false); - } else if (el.attachEvent) { // IE <= 8 - el.attachEvent('on' + eventType, handler); - } else { // ancient browsers - el['on' + eventType] = handler; - } - }, - - hasClass: function(element, my_class) { - return (" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf(" "+my_class+" ") > -1; - } - }; - - /** - * ImageLoader constructor. - * @constructor - * ImageLoader is used for determening if all the images from the Tree are loaded. - * Node size (width, height) can be correcty determined only when all inner images are loaded - */ - var ImageLoader = function() { - this.loading = []; - }; - - - ImageLoader.prototype = { - processNode: function(node) { - var images = node.nodeDOM.getElementsByTagName('img'), - i = images.length; - while(i--) { - this.create(node, images[i]); - } - }, - - removeAll: function(img_src) { - var i = this.loading.length; - while (i--) { - if (this.loading[i] === img_src) { this.loading.splice(i,1); } - } - }, - - create: function (node, image) { - - var self = this, - source = image.src; - this.loading.push(source); - - function imgTrigger() { - self.removeAll(source); - node.width = node.nodeDOM.offsetWidth; - node.height = node.nodeDOM.offsetHeight; - } - - if (image.complete) { return imgTrigger(); } - - UTIL.addEvent(image, 'load', imgTrigger); - UTIL.addEvent(image, 'error', imgTrigger); // handle broken url-s - - // load event is not fired for cached images, force the load event - image.src += "?" + new Date().getTime(); - }, - isNotLoading: function() { - return this.loading.length === 0; - } - }; - - /** - * Class: TreeStore - * @singleton - * TreeStore is used for holding initialized Tree objects - * Its purpose is to avoid global variables and enable multiple Trees on the page. - */ - - var TreeStore = { - store: [], - createTree: function(jsonConfig) { - this.store.push(new Tree(jsonConfig, this.store.length)); - return this.store[this.store.length - 1]; // return newly created tree - }, - get: function (treeId) { - return this.store[ treeId ]; - } - }; - - /** - * Tree constructor. - * @constructor - */ - var Tree = function (jsonConfig, treeId) { - - this.id = treeId; - - this.imageLoader = new ImageLoader(); - this.CONFIG = UTIL.createMerge(Tree.prototype.CONFIG, jsonConfig['chart']); - this.drawArea = document.getElementById(this.CONFIG.container.substring(1)); - this.drawArea.className += " Treant"; - this.nodeDB = new NodeDB(jsonConfig['nodeStructure'], this); - - // key store for storing reference to node connectors, - // key = nodeId where the connector ends - this.connectionStore = {}; - }; - - Tree.prototype = { - - positionTree: function(callback) { - - var self = this; - - if (this.imageLoader.isNotLoading()) { - - var root = this.root(), - orient = this.CONFIG.rootOrientation; - - this.resetLevelData(); - - this.firstWalk(root, 0); - this.secondWalk( root, 0, 0, 0 ); - - this.positionNodes(); - - if (this.CONFIG['animateOnInit']) { - setTimeout(function() { root.toggleCollapse(); }, this.CONFIG['animateOnInitDelay']); - } - - if(!this.loaded) { - this.drawArea.className += " loaded"; // nodes are hidden until .loaded class is add - if (Object.prototype.toString.call(callback) === "[object Function]") { callback(self); } - this.loaded = true; - } - - } else { - setTimeout(function() { self.positionTree(callback); }, 10); - } - }, - - /* - * In a first post-order walk, every node of the tree is - * assigned a preliminary x-coordinate (held in field - * node->flPrelim). In addition, internal nodes are - * given modifiers, which will be used to move their - * children to the right (held in field - * node->flModifier). - */ - firstWalk: function(node, level) { - - node.prelim = null; node.modifier = null; - - this.setNeighbors(node, level); - this.calcLevelDim(node, level); - - var leftSibling = node.leftSibling(); - - if(node.childrenCount() === 0 || level == this.CONFIG.maxDepth) { - // set preliminary x-coordinate - if(leftSibling) { - node.prelim = leftSibling.prelim + leftSibling.size() + this.CONFIG.siblingSeparation; - } else { - node.prelim = 0; - } - - } else { - //node is not a leaf, firstWalk for each child - for(var i = 0, n = node.childrenCount(); i < n; i++) { - this.firstWalk(node.childAt(i), level + 1); - } - - var midPoint = node.childrenCenter() - node.size() / 2; - - if(leftSibling) { - node.prelim = leftSibling.prelim + leftSibling.size() + this.CONFIG.siblingSeparation; - node.modifier = node.prelim - midPoint; - this.apportion( node, level ); - } else { - node.prelim = midPoint; - } - - // handle stacked children positioning - if(node.stackParent) { // hadle the parent of stacked children - node.modifier += this.nodeDB.get( node.stackChildren[0] ).size()/2 + node.connStyle.stackIndent; - } else if ( node.stackParentId ) { // handle stacked children - node.prelim = 0; - } - } - }, - - /* - * Clean up the positioning of small sibling subtrees. - * Subtrees of a node are formed independently and - * placed as close together as possible. By requiring - * that the subtrees be rigid at the time they are put - * together, we avoid the undesirable effects that can - * accrue from positioning nodes rather than subtrees. - */ - apportion: function (node, level) { - var firstChild = node.firstChild(), - firstChildLeftNeighbor = firstChild.leftNeighbor(), - compareDepth = 1, - depthToStop = this.CONFIG.maxDepth - level; - - while( firstChild && firstChildLeftNeighbor && compareDepth <= depthToStop ) { - // calculate the position of the firstChild, according to the position of firstChildLeftNeighbor - - var modifierSumRight = 0, - modifierSumLeft = 0, - leftAncestor = firstChildLeftNeighbor, - rightAncestor = firstChild; - - for(var i = 0; i < compareDepth; i++) { - - leftAncestor = leftAncestor.parent(); - rightAncestor = rightAncestor.parent(); - modifierSumLeft += leftAncestor.modifier; - modifierSumRight += rightAncestor.modifier; - // all the stacked children are oriented towards right so use right variables - if(rightAncestor.stackParent !== undefined) modifierSumRight += rightAncestor.size()/2; - } - - // find the gap between two trees and apply it to subTrees - // and mathing smaller gaps to smaller subtrees - - var totalGap = (firstChildLeftNeighbor.prelim + modifierSumLeft + firstChildLeftNeighbor.size() + this.CONFIG.subTeeSeparation) - (firstChild.prelim + modifierSumRight ); - - if(totalGap > 0) { - - var subtreeAux = node, - numSubtrees = 0; - - // count all the subtrees in the LeftSibling - while(subtreeAux && subtreeAux.id != leftAncestor.id) { - subtreeAux = subtreeAux.leftSibling(); - numSubtrees++; - } - - if(subtreeAux) { - - var subtreeMoveAux = node, - singleGap = totalGap / numSubtrees; - - while(subtreeMoveAux.id != leftAncestor.id) { - subtreeMoveAux.prelim += totalGap; - subtreeMoveAux.modifier += totalGap; - totalGap -= singleGap; - subtreeMoveAux = subtreeMoveAux.leftSibling(); - } - } - } - - compareDepth++; - - if(firstChild.childrenCount() === 0){ - firstChild = node.leftMost(0, compareDepth); - } else { - firstChild = firstChild.firstChild(); - } - if(firstChild) { - firstChildLeftNeighbor = firstChild.leftNeighbor(); - } - } - }, - - /* - * During a second pre-order walk, each node is given a - * final x-coordinate by summing its preliminary - * x-coordinate and the modifiers of all the node's - * ancestors. The y-coordinate depends on the height of - * the tree. (The roles of x and y are reversed for - * RootOrientations of EAST or WEST.) - */ - secondWalk: function( node, level, X, Y) { - - if(level <= this.CONFIG.maxDepth) { - var xTmp = node.prelim + X, - yTmp = Y, align = this.CONFIG.nodeAlign, - orinet = this.CONFIG.rootOrientation, - levelHeight, nodesizeTmp; - - if (orinet == 'NORTH' || orinet == 'SOUTH') { - - levelHeight = this.levelMaxDim[level].height; - nodesizeTmp = node.height; - if (node.pseudo) node.height = levelHeight; // assign a new size to pseudo nodes - } - else if (orinet == 'WEST' || orinet == 'EAST') { - - levelHeight = this.levelMaxDim[level].width; - nodesizeTmp = node.width; - if (node.pseudo) node.width = levelHeight; // assign a new size to pseudo nodes - } - - node.X = xTmp; - - if (node.pseudo) { // pseudo nodes need to be properly aligned, otherwise position is not correct in some examples - if (orinet == 'NORTH' || orinet == 'WEST') { - node.Y = yTmp; // align "BOTTOM" - } - else if (orinet == 'SOUTH' || orinet == 'EAST') { - node.Y = (yTmp + (levelHeight - nodesizeTmp)); // align "TOP" - } - - } else { - node.Y = ( align == 'CENTER' ) ? (yTmp + (levelHeight - nodesizeTmp) / 2) : - ( align == 'TOP' ) ? (yTmp + (levelHeight - nodesizeTmp)) : - yTmp; - } - - - if(orinet == 'WEST' || orinet == 'EAST') { - var swapTmp = node.X; - node.X = node.Y; - node.Y = swapTmp; - } - - if (orinet == 'SOUTH') { - - node.Y = -node.Y - nodesizeTmp; - } - else if (orinet == 'EAST') { - - node.X = -node.X - nodesizeTmp; - } - - if(node.childrenCount() !== 0) { - - if(node.id === 0 && this.CONFIG.hideRootNode) { - // ako je root node Hiden onda nemoj njegovu dijecu pomaknut po Y osi za Level separation, neka ona budu na vrhu - this.secondWalk(node.firstChild(), level + 1, X + node.modifier, Y); - } else { - - this.secondWalk(node.firstChild(), level + 1, X + node.modifier, Y + levelHeight + this.CONFIG.levelSeparation); - } - } - - if(node.rightSibling()) { - - this.secondWalk(node.rightSibling(), level, X, Y); - } - } - }, - - // position all the nodes, center the tree in center of its container - // 0,0 coordinate is in the upper left corner - positionNodes: function() { - - var self = this, - treeSize = { - x: self.nodeDB.getMinMaxCoord('X', null, null), - y: self.nodeDB.getMinMaxCoord('Y', null, null) - }, - - treeWidth = treeSize.x.max - treeSize.x.min, - treeHeight = treeSize.y.max - treeSize.y.min, - - treeCenter = { - x: treeSize.x.max - treeWidth/2, - y: treeSize.y.max - treeHeight/2 - }, - - containerCenter = { - x: self.drawArea.clientWidth/2, - y: self.drawArea.clientHeight/2 - }, - - deltaX = containerCenter.x - treeCenter.x, - deltaY = containerCenter.y - treeCenter.y, - - // all nodes must have positive X or Y coordinates, handle this with offsets - negOffsetX = ((treeSize.x.min + deltaX) <= 0) ? Math.abs(treeSize.x.min) : 0, - negOffsetY = ((treeSize.y.min + deltaY) <= 0) ? Math.abs(treeSize.y.min) : 0, - i, len, node; - - this.handleOverflow(treeWidth, treeHeight); - - // position all the nodes - for(i =0, len = this.nodeDB.db.length; i < len; i++) { - - node = this.nodeDB.get(i); - - if(node.id === 0 && this.CONFIG.hideRootNode) continue; - - // if the tree is smaller than the draw area, then center the tree within drawing area - node.X += negOffsetX + ((treeWidth < this.drawArea.clientWidth) ? deltaX : this.CONFIG.padding); - node.Y += negOffsetY + ((treeHeight < this.drawArea.clientHeight) ? deltaY : this.CONFIG.padding); - - var collapsedParent = node.collapsedParent(), - hidePoint = null; - - if(collapsedParent) { - // position the node behind the connector point of the parent, so future animations can be visible - hidePoint = collapsedParent.connectorPoint( true ); - node.hide(hidePoint); - - } else if(node.positioned) { - // node is allready positioned, - node.show(); - } else { // inicijalno stvaranje nodeova, postavi lokaciju - node.nodeDOM.style.left = node.X + 'px'; - node.nodeDOM.style.top = node.Y + 'px'; - - node.positioned = true; - } - - if (node.id !== 0 && !(node.parent().id === 0 && this.CONFIG.hideRootNode)) { - this.setConnectionToParent(node, hidePoint); // skip the root node - } - else if (!this.CONFIG.hideRootNode && node.drawLineThrough) { - // drawlinethrough is performed for for the root node also - node.drawLineThroughMe(); - } - } - - }, - - // create Raphael instance, set scrollbars if necessary - handleOverflow: function(treeWidth, treeHeight) { - - var viewWidth = (treeWidth < this.drawArea.clientWidth) ? this.drawArea.clientWidth : treeWidth + this.CONFIG.padding*2, - viewHeight = (treeHeight < this.drawArea.clientHeight) ? this.drawArea.clientHeight : treeHeight + this.CONFIG.padding*2; - - if(this._R) { - this._R.setSize(viewWidth, viewHeight); - } else { - this._R = this._R || Raphael(this.drawArea, viewWidth, viewHeight); - } - - - if(this.CONFIG.scrollbar == 'native') { - - if(this.drawArea.clientWidth < treeWidth) { // is owerflow-x necessary - this.drawArea.style.overflowX = "auto"; - } - - if(this.drawArea.clientHeight < treeHeight) { // is owerflow-y necessary - this.drawArea.style.overflowY = "auto"; - } - - } else if (this.CONFIG.scrollbar == 'fancy') { - - var jq_drawArea = $(this.drawArea); - if (jq_drawArea.hasClass('ps-container')) { // znaci da je 'fancy' vec inicijaliziran, treba updateat - - jq_drawArea.find('.Treant').css({ - width: viewWidth, - height: viewHeight - }); - - jq_drawArea.perfectScrollbar('update'); - - } else { - - var mainContiner = jq_drawArea.wrapInner('
    '), - child = mainContiner.find('.Treant'); - - child.css({ - width: viewWidth, - height: viewHeight - }); - - mainContiner.perfectScrollbar(); - } - } // else this.CONFIG.scrollbar == 'None' - - }, - - setConnectionToParent: function(node, hidePoint) { - - var stacked = node.stackParentId, - connLine, - parent = stacked ? this.nodeDB.get(stacked) : node.parent(), - - pathString = hidePoint ? this.getPointPathString(hidePoint): - this.getPathString(parent, node, stacked); - - if (this.connectionStore[node.id]) { - // connector allready exists, update the connector geometry - connLine = this.connectionStore[node.id]; - this.animatePath(connLine, pathString); - - } else { - - connLine = this._R.path( pathString ); - this.connectionStore[node.id] = connLine; - - // don't show connector arrows por pseudo nodes - if(node.pseudo) { delete parent.connStyle.style['arrow-end']; } - if(parent.pseudo) { delete parent.connStyle.style['arrow-start']; } - - connLine.attr(parent.connStyle.style); - - if(node.drawLineThrough || node.pseudo) { node.drawLineThroughMe(hidePoint); } - } - }, - - // create the parh which is represanted as a point, used for hideing the connection - getPointPathString: function(hp) { - // "_" indicates the path will be hidden - return ["_M", hp.x, ",", hp.y, 'L', hp.x, ",", hp.y, hp.x, ",", hp.y].join(" "); - }, - - animatePath: function(path, pathString) { - - if (path.hidden && pathString.charAt(0) !== "_") { // path will be shown, so show it - path.show(); - path.hidden = false; - } - - path.animate({ - path: pathString.charAt(0) === "_" ? pathString.substring(1) : pathString // remove the "_" prefix if it exists - }, this.CONFIG['animation']['connectorsSpeed'], this.CONFIG['animation']['connectorsAnimation'], - function(){ - if(pathString.charAt(0) === "_") { // animation is hideing the path, hide it at the and of animation - path.hide(); - path.hidden = true; - } - - }); - - }, - - getPathString: function(from_node, to_node, stacked) { - - var startPoint = from_node.connectorPoint( true ), - endPoint = to_node.connectorPoint( false ), - orinet = this.CONFIG.rootOrientation, - connType = from_node.connStyle.type, - P1 = {}, P2 = {}; - - if (orinet == 'NORTH' || orinet == 'SOUTH') { - P1.y = P2.y = (startPoint.y + endPoint.y) / 2; - - P1.x = startPoint.x; - P2.x = endPoint.x; - - } else if (orinet == 'EAST' || orinet == 'WEST') { - P1.x = P2.x = (startPoint.x + endPoint.x) / 2; - - P1.y = startPoint.y; - P2.y = endPoint.y; - } - - // sp, p1, pm, p2, ep == "x,y" - var sp = startPoint.x+','+startPoint.y, p1 = P1.x+','+P1.y, p2 = P2.x+','+P2.y, ep = endPoint.x+','+endPoint.y, - pm = (P1.x + P2.x)/2 +','+ (P1.y + P2.y)/2, pathString, stackPoint; - - if(stacked) { // STACKED CHILDREN - - stackPoint = (orinet == 'EAST' || orinet == 'WEST') ? - endPoint.x+','+startPoint.y : - startPoint.x+','+endPoint.y; - - if( connType == "step" || connType == "straight" ) { - - pathString = ["M", sp, 'L', stackPoint, 'L', ep]; - - } else if ( connType == "curve" || connType == "bCurve" ) { - - var helpPoint, // used for nicer curve lines - indent = from_node.connStyle.stackIndent; - - if (orinet == 'NORTH') { - helpPoint = (endPoint.x - indent)+','+(endPoint.y - indent); - } else if (orinet == 'SOUTH') { - helpPoint = (endPoint.x - indent)+','+(endPoint.y + indent); - } else if (orinet == 'EAST') { - helpPoint = (endPoint.x + indent) +','+startPoint.y; - } else if ( orinet == 'WEST') { - helpPoint = (endPoint.x - indent) +','+startPoint.y; - } - - pathString = ["M", sp, 'L', helpPoint, 'S', stackPoint, ep]; - } - - } else { // NORAML CHILDREN - - if( connType == "step" ) { - pathString = ["M", sp, 'L', p1, 'L', p2, 'L', ep]; - } else if ( connType == "curve" ) { - pathString = ["M", sp, 'C', p1, p2, ep ]; - } else if ( connType == "bCurve" ) { - pathString = ["M", sp, 'Q', p1, pm, 'T', ep]; - } else if (connType == "straight" ) { - pathString = ["M", sp, 'L', sp, ep]; - } - } - - return pathString.join(" "); - }, - - // algorithm works from left to right, so previous processed node will be left neigbor of the next node - setNeighbors: function(node, level) { - - node.leftNeighborId = this.lastNodeOnLevel[level]; - if(node.leftNeighborId) node.leftNeighbor().rightNeighborId = node.id; - this.lastNodeOnLevel[level] = node.id; - }, - - // used for calculation of height and width of a level (level dimensions) - calcLevelDim: function(node, level) { // root node is on level 0 - if (this.levelMaxDim[level]) { - if( this.levelMaxDim[level].width < node.width ) - this.levelMaxDim[level].width = node.width; - - if( this.levelMaxDim[level].height < node.height ) - this.levelMaxDim[level].height = node.height; - - } else { - this.levelMaxDim[level] = { width: node.width, height: node.height }; - } - }, - - resetLevelData: function() { - this.lastNodeOnLevel = []; - this.levelMaxDim = []; - }, - - root: function() { - return this.nodeDB.get( 0 ); - } - }; - - /** - * NodeDB constructor. - * @constructor - * NodeDB is used for storing the nodes. Each tree has its own NodeDB. - */ - var NodeDB = function (nodeStructure, tree) { - - this.db = []; - - var self = this; - - function itterateChildren(node, parentId) { - - var newNode = self.createNode(node, parentId, tree, null); - - if(node['children']) { - - newNode.children = []; - - // pseudo node is used for descending children to the next level - if(node['childrenDropLevel'] && node['childrenDropLevel'] > 0) { - while(node['childrenDropLevel']--) { - // pseudo node needs to inherit the connection style from its parent for continuous connectors - var connStyle = UTIL.cloneObj(newNode.connStyle); - newNode = self.createNode('pseudo', newNode.id, tree, null); - newNode.connStyle = connStyle; - newNode.children = []; - } - } - - var stack = (node['stackChildren'] && !self.hasGrandChildren(node)) ? newNode.id : null; - - // svildren are position on separate leves, one beneeth the other - if (stack !== null) { newNode.stackChildren = []; } - - for (var i = 0, len = node['children'].length; i < len ; i++) { - - if (stack !== null) { - newNode = self.createNode(node['children'][i], newNode.id, tree, stack); - if((i + 1) < len) newNode.children = []; // last node cant have children - } else { - itterateChildren(node['children'][i], newNode.id); - } - } - } - } - - if (tree.CONFIG['animateOnInit']) nodeStructure['collapsed'] = true; - - itterateChildren( nodeStructure, -1); // root node - - this.createGeometries(tree); - }; - - NodeDB.prototype = { - - createGeometries: function(tree) { - var i = this.db.length, node; - while(i--) { - this.get(i).createGeometry(tree); - } - }, - - get: function (nodeId) { - return this.db[nodeId]; // get node by ID - }, - - createNode: function(nodeStructure, parentId, tree, stackParentId) { - - var node = new TreeNode( nodeStructure, this.db.length, parentId, tree, stackParentId ); - - this.db.push( node ); - if( parentId >= 0 ) this.get( parentId ).children.push( node.id ); //skip root node - - if( stackParentId ) { - this.get( stackParentId ).stackParent = true; - this.get( stackParentId ).stackChildren.push( node.id ); - } - - return node; - }, - - getMinMaxCoord: function( dim, parent, MinMax ) { // used for getting the dimensions of the tree, dim = 'X' || 'Y' - // looks for min and max (X and Y) within the set of nodes - var parent = parent || this.get(0), - i = parent.childrenCount(), - MinMax = MinMax || { // start with root node dimensions - min: parent[dim], - max: parent[dim] + ((dim == 'X') ? parent.width : parent.height) - }; - - while(i--) { - - var node = parent.childAt(i), - maxTest = node[dim] + ((dim == 'X') ? node.width : node.height), - minTest = node[dim]; - - if (maxTest > MinMax.max) { - MinMax.max = maxTest; - - } - if (minTest < MinMax.min) { - MinMax.min = minTest; - } - - this.getMinMaxCoord(dim, node, MinMax); - } - return MinMax; - }, - - hasGrandChildren: function(nodeStructure) { - var i = nodeStructure.children.length; - while(i--) { - if(nodeStructure.children[i].children) return true; - } - } - }; - - - /** - * TreeNode constructor. - * @constructor - */ - var TreeNode = function (nodeStructure, id, parentId, tree, stackParentId) { - - this.id = id; - this.parentId = parentId; - this.treeId = tree.id; - this.prelim = 0; - this.modifier = 0; - - this.stackParentId = stackParentId; - - // pseudo node is a node with width=height=0, it is invisible, but necessary for the correct positiong of the tree - this.pseudo = nodeStructure === 'pseudo' || nodeStructure['pseudo']; - - this.image = nodeStructure['image']; - - this.link = UTIL.createMerge( tree.CONFIG.node.link, nodeStructure['link']); - - this.connStyle = UTIL.createMerge(tree.CONFIG.connectors, nodeStructure['connectors']); - - this.drawLineThrough = nodeStructure['drawLineThrough'] === false ? false : nodeStructure['drawLineThrough'] || tree.CONFIG.node['drawLineThrough']; - - this.collapsable = nodeStructure['collapsable'] === false ? false : nodeStructure['collapsable'] || tree.CONFIG.node['collapsable']; - this.collapsed = nodeStructure['collapsed']; - - this.text = nodeStructure['text']; - - // '.node' DIV - this.nodeInnerHTML = nodeStructure['innerHTML']; - this.nodeHTMLclass = (tree.CONFIG.node['HTMLclass'] ? tree.CONFIG.node['HTMLclass'] : '') + // globaly defined class for the nodex - (nodeStructure['HTMLclass'] ? (' ' + nodeStructure['HTMLclass']) : ''); // + specific node class - - this.nodeHTMLid = nodeStructure['HTMLid']; - }; - - TreeNode.prototype = { - - Tree: function() { - return TreeStore.get(this.treeId); - }, - - dbGet: function(nodeId) { - return this.Tree().nodeDB.get(nodeId); - }, - - size: function() { // returns the width of the node - var orient = this.Tree().CONFIG.rootOrientation; - - if(this.pseudo) return - this.Tree().CONFIG.subTeeSeparation; // prevents of separateing the subtrees - - if (orient == 'NORTH' || orient == 'SOUTH') - return this.width; - - else if (orient == 'WEST' || orient == 'EAST') - return this.height; - }, - - childrenCount: function () { - return (this.collapsed || !this.children) ? 0 : this.children.length; - }, - - childAt: function(i) { - return this.dbGet( this.children[i] ); - }, - - firstChild: function() { - return this.childAt(0); - }, - - lastChild: function() { - return this.childAt( this.children.length - 1 ); - }, - - parent: function() { - return this.dbGet( this.parentId ); - }, - - leftNeighbor: function() { - if( this.leftNeighborId ) return this.dbGet( this.leftNeighborId ); - }, - - rightNeighbor: function() { - if( this.rightNeighborId ) return this.dbGet( this.rightNeighborId ); - }, - - leftSibling: function () { - var leftNeighbor = this.leftNeighbor(); - - if( leftNeighbor && leftNeighbor.parentId == this.parentId ) return leftNeighbor; - }, - - rightSibling: function () { - var rightNeighbor = this.rightNeighbor(); - - if( rightNeighbor && rightNeighbor.parentId == this.parentId ) return rightNeighbor; - }, - - childrenCenter: function ( tree ) { - var first = this.firstChild(), - last = this.lastChild(); - return first.prelim + ((last.prelim - first.prelim) + last.size()) / 2; - }, - - // find out if one of the node ancestors is collapsed - collapsedParent: function() { - var parent = this.parent(); - if (!parent) return false; - if (parent.collapsed) return parent; - return parent.collapsedParent(); - }, - - leftMost: function ( level, depth ) { // returns the leftmost child at specific level, (initaial level = 0) - - if( level >= depth ) return this; - if( this.childrenCount() === 0 ) return; - - for(var i = 0, n = this.childrenCount(); i < n; i++) { - - var leftmostDescendant = this.childAt(i).leftMost( level + 1, depth ); - if(leftmostDescendant) - return leftmostDescendant; - } - }, - - // returns start or the end point of the connector line, origin is upper-left - connectorPoint: function(startPoint) { - var orient = this.Tree().CONFIG.rootOrientation, point = {}; - - if(this.stackParentId) { // return different end point if node is a stacked child - if (orient == 'NORTH' || orient == 'SOUTH') { orient = 'WEST'; } - else if (orient == 'EAST' || orient == 'WEST') { orient = 'NORTH'; } - } - // if pseudo, a virtual center is used - if (orient == 'NORTH') { - - point.x = (this.pseudo) ? this.X - this.Tree().CONFIG.subTeeSeparation/2 : this.X + this.width/2; - point.y = (startPoint) ? this.Y + this.height : this.Y; - - } else if (orient == 'SOUTH') { - - point.x = (this.pseudo) ? this.X - this.Tree().CONFIG.subTeeSeparation/2 : this.X + this.width/2; - point.y = (startPoint) ? this.Y : this.Y + this.height; - - } else if (orient == 'EAST') { - - point.x = (startPoint) ? this.X : this.X + this.width; - point.y = (this.pseudo) ? this.Y - this.Tree().CONFIG.subTeeSeparation/2 : this.Y + this.height/2; - - } else if (orient == 'WEST') { - - point.x = (startPoint) ? this.X + this.width : this.X; - point.y = (this.pseudo) ? this.Y - this.Tree().CONFIG.subTeeSeparation/2 : this.Y + this.height/2; - } - return point; - }, - - pathStringThrough: function() { // get the geometry of a path going through the node - var startPoint = this.connectorPoint(true), - endPoint = this.connectorPoint(false); - - return ["M", startPoint.x+","+startPoint.y, 'L', endPoint.x+","+endPoint.y].join(" "); - }, - - drawLineThroughMe: function(hidePoint) { // hidepoint se proslijedjuje ako je node sakriven zbog collapsed - - var pathString = hidePoint ? this.Tree().getPointPathString(hidePoint) : this.pathStringThrough(); - - this.lineThroughMe = this.lineThroughMe || this.Tree()._R.path(pathString); - - var line_style = UTIL.cloneObj(this.connStyle.style); - - delete line_style['arrow-start']; - delete line_style['arrow-end']; - - this.lineThroughMe.attr( line_style ); - - if(hidePoint) { - this.lineThroughMe.hide(); - this.lineThroughMe.hidden = true; - } - }, - - addSwitchEvent: function(my_switch) { - var self = this; - UTIL.addEvent(my_switch, 'click', function(){ - self.toggleCollapse(); - }); - }, - - toggleCollapse: function() { - var tree = this.Tree(); - - if (! tree.inAnimation) { - - tree.inAnimation = true; - - this.collapsed = !this.collapsed; // toglle the collapse at each click - if (this.collapsed) { - $(this.nodeDOM).addClass('collapsed'); - } else { - $(this.nodeDOM).removeClass('collapsed'); - } - tree.positionTree(); - - setTimeout(function() { // set the flag after the animation - tree.inAnimation = false; - }, tree.CONFIG['animation']['nodeSpeed'] > tree.CONFIG['animation']['connectorsSpeed'] ? tree.CONFIG['animation']['nodeSpeed'] : tree.CONFIG['animation']['connectorsSpeed']) - } - }, - - hide: function(collapse_to_point) { - this.nodeDOM.style.overflow = "hidden"; - - var jq_node = $(this.nodeDOM), tree = this.Tree(), - config = tree.CONFIG, - new_pos = { - left: collapse_to_point.x, - top: collapse_to_point.y - }; - - if (!this.hidden) { new_pos.width = new_pos.height = 0; } - - // store old width + height - padding problem when returning back to old state - if(!this.startW || !this.startH) { this.startW = jq_node.width(); this.startH = jq_node.height(); } - - // if parent was hidden in initial configuration, position the node behind the parent without animations - if(!this.positioned || this.hidden) { - this.nodeDOM.style.visibility = 'hidden'; - jq_node.css(new_pos); - this.positioned = true; - } else { - jq_node.animate(new_pos, config['animation']['nodeSpeed'], config['animation']['nodeAnimation'], - function(){ - this.style.visibility = 'hidden'; - }); - } - - // animate the line through node if the line exists - if(this.lineThroughMe) { - var new_path = tree.getPointPathString(collapse_to_point); - if (this.hidden) { - // update without animations - this.lineThroughMe.attr({path: new_path}); - } else { - // update with animations - tree.animatePath(this.lineThroughMe, tree.getPointPathString(collapse_to_point)); - } - } - - this.hidden = true; - }, - - show: function() { - this.nodeDOM.style.visibility = 'visible'; - - var new_pos = { - left: this.X, - top: this.Y - }, - tree = this.Tree(), config = tree.CONFIG; - - // if the node was hidden, update width and height - if(this.hidden) { - new_pos['width'] = this.startW; - new_pos['height'] = this.startH; - } - - $(this.nodeDOM).animate( - new_pos, - config['animation']['nodeSpeed'], config['animation']['nodeAnimation'], - function() { - // $.animate applys "overflow:hidden" to the node, remove it to avoid visual problems - this.style.overflow = ""; - } - ); - - if(this.lineThroughMe) { - tree.animatePath(this.lineThroughMe, this.pathStringThrough()); - } - - this.hidden = false; - } - }; - - TreeNode.prototype.createGeometry = function(tree) { - - if (this.id === 0 && tree.CONFIG.hideRootNode) { - this.width = 0; this.height = 0; - return; - } - - var drawArea = tree.drawArea, - image, i, - - /////////// CREATE NODE ////////////// - node = this.link.href ? document.createElement('a') : document.createElement('div'); - - node.className = (!this.pseudo) ? TreeNode.prototype.CONFIG.nodeHTMLclass : 'pseudo'; - if(this.nodeHTMLclass && !this.pseudo) node.className += ' ' + this.nodeHTMLclass; - - if(this.nodeHTMLid) node.id = this.nodeHTMLid; - - if(this.link.href) { - node.href = this.link.href; - node.target = this.link.target; - } - - /////////// CREATE innerHTML ////////////// - if (!this.pseudo) { - if (!this.nodeInnerHTML) { - - // IMAGE - if(this.image) { - image = document.createElement('img'); - - image.src = this.image; - node.appendChild(image); - } - - // TEXT - if(this.text) { - for(var key in this.text) { - if(TreeNode.prototype.CONFIG.textClass[key]) { - var text = document.createElement(this.text[key].href ? 'a' : 'p'); - - // meke an element if required - if (this.text[key].href) { - text.href = this.text[key].href; - if (this.text[key].target) { text.target = this.text[key].target; } - } - //.split('<br>').join('
    ') - text.className = TreeNode.prototype.CONFIG.textClass[key]; - text.appendChild(document.createTextNode( - this.text[key].val ? this.text[key].val : - this.text[key] instanceof Object ? "'val' param missing!" : this.text[key] - )); - text.innerHTML = text.innerHTML.split('<br>').join('
    ') - - node.appendChild(text); - } - } - } - - } else { - - // get some element by ID and clone its structure into a node - if (this.nodeInnerHTML.charAt(0) === "#") { - var elem = document.getElementById(this.nodeInnerHTML.substring(1)); - if (elem) { - node = elem.cloneNode(true); - node.id += "-clone"; - node.className += " node"; - } else { - node.innerHTML = " Wrong ID selector "; - } - } else { - // insert your custom HTML into a node - node.innerHTML = this.nodeInnerHTML; - } - } - - // handle collapse switch - if (this.collapsed || (this.collapsable && this.childrenCount() && !this.stackParentId)) { - var my_switch = document.createElement('a'); - my_switch.className = "collapse-switch"; - node.appendChild(my_switch); - this.addSwitchEvent(my_switch); - if (this.collapsed) { node.className += " collapsed"; } - } - } - - /////////// APPEND all ////////////// - drawArea.appendChild(node); - - this.width = node.offsetWidth; - this.height = node.offsetHeight; - - this.nodeDOM = node; - - tree.imageLoader.processNode(this); - }; - - - - // ########################################### - // Expose global + default CONFIG params - // ########################################### - - Tree.prototype.CONFIG = { - 'maxDepth': 100, - 'rootOrientation': 'NORTH', // NORTH || EAST || WEST || SOUTH - 'nodeAlign': 'CENTER', // CENTER || TOP || BOTTOM - 'levelSeparation': 30, - 'siblingSeparation': 30, - 'subTeeSeparation': 30, - - 'hideRootNode': false, - - 'animateOnInit': false, - 'animateOnInitDelay': 500, - - 'padding': 15, // the difference is seen only when the scrollbar is shown - 'scrollbar': "native", // "native" || "fancy" || "None" (PS: "fancy" requires jquery and perfect-scrollbar) - - 'connectors': { - - 'type': 'curve', // 'curve' || 'step' || 'straight' || 'bCurve' - 'style': { - 'stroke': 'black' - }, - 'stackIndent': 15 - }, - - 'node': { // each node inherits this, it can all be overrifen in node config - - // HTMLclass: 'node', - // drawLineThrough: false, - // collapsable: false, - 'link': { - 'target': "_self" - } - }, - - 'animation': { // each node inherits this, it can all be overrifen in node config - - 'nodeSpeed': 450, - 'nodeAnimation': "linear", - 'connectorsSpeed': 450, - 'connectorsAnimation': "linear" - } - }; - - TreeNode.prototype.CONFIG = { - 'nodeHTMLclass': 'node', - - 'textClass': { - 'name': 'node-name', - 'title': 'node-title', - 'desc': 'node-desc', - 'contact': 'node-contact' - } - }; - - // ############################################# - // Makes a JSON chart config out of Array config - // ############################################# - - var JSOnconfig = { - make: function( configArray ) { - - var i = configArray.length, node; - - this.jsonStructure = { - 'chart': null, - 'nodeStructure': null - }; - //fist loop: find config, find root; - while(i--) { - node = configArray[i]; - if (node.hasOwnProperty('container')) { - this.jsonStructure.chart = node; - continue; - } - - if (!node.hasOwnProperty('parent') && ! node.hasOwnProperty('container')) { - this.jsonStructure.nodeStructure = node; - node.myID = this.getID(); - } - } - - this.findChildren(configArray); - - return this.jsonStructure; - }, - - findChildren: function(nodes) { - var parents = [0]; // start witha a root node - - while(parents.length) { - var parentId = parents.pop(), - parent = this.findNode(this.jsonStructure.nodeStructure, parentId), - i = 0, len = nodes.length, - children = []; - - for(;i Date: Tue, 29 Nov 2022 14:22:25 +0100 Subject: [PATCH 02/26] made a yellow background. Up next, the timing table on the left --- scripts/generate_querygraph.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/generate_querygraph.py b/scripts/generate_querygraph.py index 460bfd21f938..88ad7337c434 100644 --- a/scripts/generate_querygraph.py +++ b/scripts/generate_querygraph.py @@ -44,10 +44,12 @@ def get_node_body(name, result, cardinality, timing, extra_info): body = "" body += "
    " body += f"

    {name}

    " - body += f"

    {result}

    " - body += f"

    {timing}s

    " + body += f"

    {result}s

    " + if timing: + body += f"

    {timing}

    " body += f"

    cardinality = {cardinality}

    " - body += f"

    {extra_info}

    " + if extra_info: + body += f"

    {extra_info}

    " body += "
    " body += "
    " return body @@ -128,6 +130,7 @@ def generate(input_file, output_file): position: relative; width: 250px; text-align: center; + background-color: #fff100; } From 15e07bfc4f71f22f85b0fb8950e1d16795dcdb6b Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Wed, 30 Nov 2022 16:48:29 +0100 Subject: [PATCH 03/26] adding table of timings --- scripts/generate_querygraph.py | 82 ++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/scripts/generate_querygraph.py b/scripts/generate_querygraph.py index 88ad7337c434..73d6ba465cfc 100644 --- a/scripts/generate_querygraph.py +++ b/scripts/generate_querygraph.py @@ -72,6 +72,42 @@ def generate_tree_recursive(json_graph): children_html += "" return node_prefix_html + node_body + children_html + node_suffix_html +def gather_timing_information(json): + # add up all of the times + # measure each time as a percentage of the total time. + # then you can return a list of [phase, time, percentage] + pass + + +def generate_timing_html(graph_json): + json_graph = json.loads(graph_json) + # gather all the timings and make a nice thing from it. + table_prefix = "" + table_head = """ + + + + + + + """ + for + return """ +
    PhaseTimePercentage
    + + + + + + + + + + + + +
    Dom6000
    Melissa5150
    +""" def generate_tree_html(graph_json): @@ -111,11 +147,12 @@ def generate(input_file, output_file): text = f.read() html_output = generate_style_html(text, True) + timing_table = generate_timing_html(text) tree_output = generate_tree_html(text) - # print(html_output['chart_script']) + # finally create and write the html with open_utf8(output_file, "w+") as f: - f.write(""" + html = """ @@ -123,6 +160,35 @@ def generate(input_file, output_file): Query Profile Graph for Query ${CSS} " + + graph_json = graph_json.replace('\n', ' ').replace("'", "\\'").replace("\\n", "\\\\n") + + chart_script = """ + """.replace("${GRAPH_JSON}", graph_json).replace("${META_INFO}", "" if not include_meta_info else "document.getElementById('meta-info').innerHTML = meta_info;") + return { + 'css': css, + 'libraries': libraries, + 'chart_script': chart_script + } + +def generate_ipython(json_input): + from IPython.core.display import HTML + + html_output = generate_html(json_input, False) + + return HTML(""" + ${CSS} + ${LIBRARIES} +
    + ${CHART_SCRIPT} + """.replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', html_output['chart_script']).replace('${LIBRARIES}', html_output['libraries'])) + + +def generate(input_file, output_file): + with open_utf8(input_file, 'r') as f: + text = f.read() + + html_output = generate_html(text, True) + print(html_output['chart_script']) + # finally create and write the html + with open_utf8(output_file, "w+") as f: + f.write(""" + + + + + Query Profile Graph for Query + ${CSS} + + + ${LIBRARIES} + +
    +
    + + ${CHART_SCRIPT} + + +""".replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', html_output['chart_script']).replace('${LIBRARIES}', html_output['libraries'])) diff --git a/tools/pythonpkg/duckdb_query_graph/parse_profiling_output.js b/tools/pythonpkg/duckdb_query_graph/parse_profiling_output.js new file mode 100644 index 000000000000..a079fe2d007a --- /dev/null +++ b/tools/pythonpkg/duckdb_query_graph/parse_profiling_output.js @@ -0,0 +1,306 @@ + + +function gather_nodes(node, node_list, node_timings) { + var node_timing = node["timing"]; + var node_name = node["name"]; + var node_children = node["children"]; + if (node_timings[node_name] == undefined) { + node_timings[node_name] = 0 + } + node_timings[node_name] += node_timing; + node_list.push([node_timing, node]); + var total_time = 0; + for(var child in node_children) { + total_time += gather_nodes(node_children[child], node_list, node_timings); + } + return total_time + node_timing; +} + +function timing_to_str(timing) { + var timing_str = timing.toFixed(3); + if (timing_str == "0.000") { + return "0.0"; + } + return timing_str; +} + +function to_percentage(total_time, time) { + if (total_time == 0) { + return "?%"; + } + var fraction = 100 * (time / total_time); + if (fraction < 0.1) { + return "0%"; + } else { + return fraction.toFixed(1) + "%"; + } +} + +function add_spaces_to_big_number(number) { + if (number.length > 4) { + return add_spaces_to_big_number(number.substr(0, number.length - 3)) + " " + number.substr(number.length - 3, number.length) + } else { + return number; + } +} + +function toTitleCase(str) { + return str.replace( + /\w\S*/g, + function(txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + } + ); +} + +function parse_profiling_output(graph_json) { + if (graph_json == null || graph_json == undefined) { + return ["No profile input.", null]; + } + var n = graph_json.indexOf('"result"'); + if (n < 0) { + // not a result we can do anything with + return [graph_json, null]; + } + // find the second occurrence of "result", if any + var second_n = graph_json.indexOf('"result"', n + 1) + if (second_n >= 0) { + // found one: find the last { before "result" + var split_index = graph_json.lastIndexOf("{", second_n); + graph_json = graph_json.substring(0, split_index); + } + // now parse the json + graph_data = JSON.parse(graph_json); + // tones for the different nodes, we use "earth tones" + tones = [ + "#493829", + "#816C5B", + "#A9A18C", + "#613318", + "#B99C6B", + "#A15038", + "#8F3B1B", + "#D57500", + "#DBCA69", + "#404F24", + "#668D3C", + "#BDD09F", + "#4E6172", + "#83929F", + "#A3ADB8" + ]; + // set fixed tones for the various common operators + fixed_tone_map = { + "ORDER_BY": 8, + "HASH_GROUP_BY": 7, + "FILTER": 13, + "SEQ_SCAN": 4, + "HASH_JOIN": 10, + "PROJECTION": 5, + "LIMIT": 0, + "PIECEWISE_MERGE_JOIN": 1, + "DISTINCT": 11, + "DELIM_SCAN": 9 + }; + // remaining (unknown) operators just fetch tones in order + var used_tones = {} + var remaining_tones = [] + for(var entry in fixed_tone_map) { + used_tones[fixed_tone_map[entry]] = true; + } + for(var tone in tones) { + if (used_tones[tone] !== undefined) { + remaining_tones.push(tone); + } + } + // first parse the total time + var total_time = graph_data["result"].toFixed(2); + + // assign an impact factor to nodes + // this is used for rendering opacity + var root_node = graph_data; + var node_list = [] + var node_timings = {} + var execution_time = gather_nodes(root_node, node_list, node_timings); + + // get the timing of the different query phases + var meta_info = ""; + var meta_timings = graph_data["timings"]; + if (meta_timings !== undefined) { + var keys = []; + var timings = {}; + for(var entry in meta_timings) { + timings[entry] = meta_timings[entry]; + keys.push(entry); + } + keys.sort(); + meta_info += ` + + + + +`; + // add the total time + meta_info += `` + // add the execution phase + var execution_time_percentage = to_percentage(total_time, execution_time); + meta_info += `` + execution_nodes = []; + for(var execution_node in node_timings) { + execution_nodes.push(execution_node); + } + execution_nodes.sort(function(a, b) { + var a_sort = node_timings[a]; + var b_sort = node_timings[b]; + if (a_sort < b_sort) { + return 1; + } else if (a_sort > b_sort) { + return -1; + } + return 0; + }); + for(var node_index in execution_nodes) { + var node = execution_nodes[node_index]; + var node_name = node; + var node_time = timing_to_str(node_timings[node]); + var node_percentage = to_percentage(total_time, node_timings[node]); + meta_info += `` + } + // add the planning/optimizer phase to the table + for(var key_index in keys) { + var key = keys[key_index]; + var is_subphase = key.includes(">"); + row_class = is_subphase ? 'subphase' : 'mainphase' + meta_info += `` + meta_info += '" + meta_info += '" + meta_info += "" + meta_info += "" + } + meta_info += '
    PhaseTimePercentage
    Total Time${total_time}s
    Execution${execution_time}s${execution_time_percentage}
    ${node_name}${node_time}s${node_percentage}
    ' + if (is_subphase) { + // subphase + var splits = key.split(" > "); + meta_info += "> " + toTitleCase(splits[splits.length - 1]).replace("_", " ") + } else { + // primary phase + meta_info += toTitleCase(key).replace("_", " ") + } + meta_info += "' + timing_to_str(timings[key]) + "s" + "" + to_percentage(total_time, timings[key]) + "
    ' + } + + // assign impacts based on % of execution spend in the node + // <0.1% = 0 + // <1% = 1 + // <5% = 2 + // anything else: 3 + for(var i = 0; i < node_list.length; i++) { + percentage = 100 * (node_list[i][0] / total_time) + if (percentage < 0.1) { + node_list[i][1]["impact"] = 0 + } else if (percentage < 1) { + node_list[i][1]["impact"] = 1 + } else if (percentage < 5) { + node_list[i][1]["impact"] = 2 + } else { + node_list[i][1]["impact"] = 3 + } + } + // now convert the JSON to the format used by treant.js + var parameters = { + "total_nodes": 0, + "current_tone": 0, + "max_nodes": 1000 + }; + + function convert_json(node, parameters) { + var node_name = node["name"]; + var node_timing = node["timing"]; + var node_extra_info = node["extra_info"] || ''; + var node_impact = node["impact"]; + var node_children = node["children"]; + parameters["total_nodes"] += 1 + // format is { text { name, title, contact }, HTMLclass, children: [...] } + var result = {} + var text_node = {} + title = "" + splits = node_extra_info.split('\n') + for(var i = 0; i < splits.length; i++) { + if (splits[i].length > 0) { + var text = splits[i].replace('"', ''); + if (text == "[INFOSEPARATOR]") { + text = "- - - - - -"; + } + title += text + "
    " + } + } + text_node["name"] = node_name + ' (' + node_timing + 's)'; + text_node["title"] = title; + text_node["contact"] = add_spaces_to_big_number(node["cardinality"]); + result["text"] = text_node; + if (fixed_tone_map[node_name] == undefined) { + fixed_tone_map[node_name] = remaining_tones[parameters["current_tone"]] + parameters["current_tone"] += 1 + if (parameters["current_tone"] >= remaining_tones.length) { + parameters["current_tone"] = 0 + } + } + if (node_impact == 0) { + impact_class = "small-impact" + } else if (node_impact == 1) { + impact_class = "medium-impact" + } else if (node_impact == 2) { + impact_class = "high-impact" + } else { + impact_class = "most-impact" + } + result["HTMLclass"] = 'impact_class ' + ' tone-' + fixed_tone_map[node_name]; + var result_children = [] + for(var i = 0; i < node_children.length; i++) { + result_children.push(convert_json(node_children[i], parameters)) + if (parameters["total_nodes"] > parameters["max_nodes"]) { + break; + } + } + result["children"] = result_children; + return result + } + + var new_json = convert_json(graph_data, parameters); + return [meta_info, new_json]; +} + +function create_graph(graph_data, container_id, container_class) { + var chart_config = { + chart: { + container: container_id, + nodeAlign: "BOTTOM", + connectors: { + type: 'step' + }, + node: { + HTMLclass: 'tree-node' + } + }, + nodeStructure: graph_data + }; + // create a dummy chart to figure out how wide/high it is + new Treant(chart_config ); + max_height = 0 + document.querySelectorAll('.tree-node').forEach(function(node) { + top_px = parseInt(node.style.top.substring(0, node.style.top.length - 2)) + if (top_px > max_height) { + max_height = top_px + } + }); + // now remove the chart we just created + document.querySelectorAll(container_id).forEach(function(node) { + node.innerHTML = "" + }); + // resize the chart + document.querySelectorAll(container_class).forEach(function(node) { + node.style.height = (max_height + 200) + "px" + }); + // and recreate it + new Treant(chart_config ); +}; diff --git a/tools/pythonpkg/duckdb_query_graph/query_graph.css b/tools/pythonpkg/duckdb_query_graph/query_graph.css new file mode 100644 index 000000000000..99f4d83f63de --- /dev/null +++ b/tools/pythonpkg/duckdb_query_graph/query_graph.css @@ -0,0 +1,136 @@ + +.Treant { position: relative; overflow: hidden; padding: 0 !important; } +.Treant > .node, +.Treant > .pseudo { position: absolute; display: block; visibility: hidden; } +.Treant.loaded .node, +.Treant.loaded .pseudo { visibility: visible; } +.Treant > .pseudo { width: 0; height: 0; border: none; padding: 0; } +.Treant .collapse-switch { width: 3px; height: 3px; display: block; border: 1px solid black; position: absolute; top: 1px; right: 1px; cursor: pointer; } +.Treant .collapsed .collapse-switch { background-color: #868DEE; } +.Treant > .node img { border: none; float: left; } + +body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td { margin:0; padding:0; } +table { border-collapse:collapse; border-spacing:0; } +fieldset,img { border:0; } +address,caption,cite,code,dfn,em,strong,th,var { font-style:normal; font-weight:normal; } +caption,th { text-align:left; } +h1,h2,h3,h4,h5,h6 { font-size:100%; font-weight:normal; } +q:before,q:after { content:''; } +abbr,acronym { border:0; } + +body { background: #fff; } +/* optional Container STYLES */ +.chart { height: 500px; margin: 5px; width: 100%; } +.Treant > .node { } +.Treant > p { font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-weight: bold; font-size: 12px; } + +.tree-node { + padding: 2px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + background-color: #ffffff; + border: 1px solid #000; + width: 200px; + font-family: Tahoma; + font-size: 12px; +} + +.metainfo { + font-family: Tahoma; + font-weight: bold; +} + +.metainfo > th { + padding: 1px 5px; +} + +.mainphase { + background-color: #668D3C; +} +.subphase { + background-color: #B99C6B; +} +.phaseheader { + background-color: #83929F; +} + +.node-name { + font-weight: bold; + text-align: center; + font-size: 14px; +} + +.node-title { + text-align: center; +} + +.node-contact { + font-weight: bold; + text-align: center; +} +th { + background:none; +} +.small-impact { + opacity: 0.7; +} + +.medium-impact { + opacity: 0.8; +} + +.high-impact { + opacity: 0.9; +} + +.most-impact { + opacity: 1; +} + +.tone-1 { + background-color: #493829; +} +.tone-2 { + background-color: #816C5B; +} +.tone-3 { + background-color: #A9A18C; +} +.tone-4 { + background-color: #613318; +} +.tone-5 { + background-color: #B99C6B; +} +.tone-6 { + background-color: #A15038; +} +.tone-7 { + background-color: #8F3B1B; +} +.tone-8 { + background-color: #D57500; +} +.tone-9 { + background-color: #DBCA69; +} +.tone-10 { + background-color: #404F24; +} +.tone-11 { + background-color: #668D3C; +} +.tone-12 { + background-color: #BDD09F; +} +.tone-13 { + background-color: #4E6172; +} +.tone-14 { + background-color: #83929F; +} +.tone-15 { + background-color: #A3ADB8; +} + diff --git a/tools/pythonpkg/duckdb_query_graph/raphael.js b/tools/pythonpkg/duckdb_query_graph/raphael.js new file mode 100644 index 000000000000..592a6f7561c1 --- /dev/null +++ b/tools/pythonpkg/duckdb_query_graph/raphael.js @@ -0,0 +1,10 @@ +// ┌────────────────────────────────────────────────────────────────────┐ \\ +// │ Raphaël 2.1.0 - JavaScript Vector Library │ \\ +// ├────────────────────────────────────────────────────────────────────┤ \\ +// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com) │ \\ +// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com) │ \\ +// ├────────────────────────────────────────────────────────────────────┤ \\ +// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\ +// └────────────────────────────────────────────────────────────────────┘ \\ +(function(n){var e,t,r="0.4.2",f="hasOwnProperty",i=/[\.\/]/,o="*",u=function(){},l=function(n,e){return n-e},s={n:{}},p=function(n,r){n+="";var f,i=t,o=Array.prototype.slice.call(arguments,2),u=p.listeners(n),s=0,a=[],c={},h=[],d=e;e=n,t=0;for(var g=0,v=u.length;v>g;g++)"zIndex"in u[g]&&(a.push(u[g].zIndex),0>u[g].zIndex&&(c[u[g].zIndex]=u[g]));for(a.sort(l);0>a[s];)if(f=c[a[s++]],h.push(f.apply(r,o)),t)return t=i,h;for(g=0;v>g;g++)if(f=u[g],"zIndex"in f)if(f.zIndex==a[s]){if(h.push(f.apply(r,o)),t)break;do if(s++,f=c[a[s]],f&&h.push(f.apply(r,o)),t)break;while(f)}else c[f.zIndex]=f;else if(h.push(f.apply(r,o)),t)break;return t=i,e=d,h.length?h:null};p._events=s,p.listeners=function(n){var e,t,r,f,u,l,p,a,c=n.split(i),h=s,d=[h],g=[];for(f=0,u=c.length;u>f;f++){for(a=[],l=0,p=d.length;p>l;l++)for(h=d[l].n,t=[h[c[f]],h[o]],r=2;r--;)e=t[r],e&&(a.push(e),g=g.concat(e.f||[]));d=a}return g},p.on=function(n,e){if(n+="","function"!=typeof e)return function(){};for(var t=n.split(i),r=s,f=0,o=t.length;o>f;f++)r=r.n,r=r.hasOwnProperty(t[f])&&r[t[f]]||(r[t[f]]={n:{}});for(r.f=r.f||[],f=0,o=r.f.length;o>f;f++)if(r.f[f]==e)return u;return r.f.push(e),function(n){+n==+n&&(e.zIndex=+n)}},p.f=function(n){var e=[].slice.call(arguments,1);return function(){p.apply(null,[n,null].concat(e).concat([].slice.call(arguments,0)))}},p.stop=function(){t=1},p.nt=function(n){return n?RegExp("(?:\\.|\\/|^)"+n+"(?:\\.|\\/|$)").test(e):e},p.nts=function(){return e.split(i)},p.off=p.unbind=function(n,e){if(!n)return p._events=s={n:{}},void 0;var t,r,u,l,a,c,h,d=n.split(i),g=[s];for(l=0,a=d.length;a>l;l++)for(c=0;g.length>c;c+=u.length-2){if(u=[c,1],t=g[c].n,d[l]!=o)t[d[l]]&&u.push(t[d[l]]);else for(r in t)t[f](r)&&u.push(t[r]);g.splice.apply(g,u)}for(l=0,a=g.length;a>l;l++)for(t=g[l];t.n;){if(e){if(t.f){for(c=0,h=t.f.length;h>c;c++)if(t.f[c]==e){t.f.splice(c,1);break}!t.f.length&&delete t.f}for(r in t.n)if(t.n[f](r)&&t.n[r].f){var v=t.n[r].f;for(c=0,h=v.length;h>c;c++)if(v[c]==e){v.splice(c,1);break}!v.length&&delete t.n[r].f}}else{delete t.f;for(r in t.n)t.n[f](r)&&t.n[r].f&&delete t.n[r].f}t=t.n}},p.once=function(n,e){var t=function(){return p.unbind(n,t),e.apply(this,arguments)};return p.on(n,t)},p.version=r,p.toString=function(){return"You are running Eve "+r},"undefined"!=typeof module&&module.exports?module.exports=p:"undefined"!=typeof define?define("eve",[],function(){return p}):n.eve=p})(this);(function(t,e){"function"==typeof define&&define.amd?define("raphael",["eve"],e):t.Raphael=e(t.eve)})(this,function(t){function e(n){if(e.is(n,"function"))return y?n():t.on("raphael.DOMload",n);if(e.is(n,W))return e._engine.create[T](e,n.splice(0,3+e.is(n[0],G))).add(n);var r=Array.prototype.slice.call(arguments,0);if(e.is(r[r.length-1],"function")){var i=r.pop();return y?i.call(e._engine.create[T](e,r)):t.on("raphael.DOMload",function(){i.call(e._engine.create[T](e,r))})}return e._engine.create[T](e,arguments)}function n(t){if(Object(t)!==t)return t;var e=new t.constructor;for(var r in t)t[B](r)&&(e[r]=n(t[r]));return e}function r(t,e){for(var n=0,r=t.length;r>n;n++)if(t[n]===e)return t.push(t.splice(n,1)[0])}function i(t,e,n){function i(){var a=Array.prototype.slice.call(arguments,0),s=a.join("␀"),o=i.cache=i.cache||{},u=i.count=i.count||[];return o[B](s)?(r(u,s),n?n(o[s]):o[s]):(u.length>=1e3&&delete o[u.shift()],u.push(s),o[s]=t[T](e,a),n?n(o[s]):o[s])}return i}function a(){return this.hex}function s(t,e){for(var n=[],r=0,i=t.length;i-2*!e>r;r+=2){var a=[{x:+t[r-2],y:+t[r-1]},{x:+t[r],y:+t[r+1]},{x:+t[r+2],y:+t[r+3]},{x:+t[r+4],y:+t[r+5]}];e?r?i-4==r?a[3]={x:+t[0],y:+t[1]}:i-2==r&&(a[2]={x:+t[0],y:+t[1]},a[3]={x:+t[2],y:+t[3]}):a[0]={x:+t[i-2],y:+t[i-1]}:i-4==r?a[3]=a[2]:r||(a[0]={x:+t[r],y:+t[r+1]}),n.push(["C",(-a[0].x+6*a[1].x+a[2].x)/6,(-a[0].y+6*a[1].y+a[2].y)/6,(a[1].x+6*a[2].x-a[3].x)/6,(a[1].y+6*a[2].y-a[3].y)/6,a[2].x,a[2].y])}return n}function o(t,e,n,r,i){var a=-3*e+9*n-9*r+3*i,s=t*a+6*e-12*n+6*r;return t*s-3*e+3*n}function u(t,e,n,r,i,a,s,u,l){null==l&&(l=1),l=l>1?1:0>l?0:l;for(var h=l/2,c=12,f=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],p=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],d=0,g=0;c>g;g++){var x=h*f[g]+h,v=o(x,t,n,i,s),m=o(x,e,r,a,u),y=v*v+m*m;d+=p[g]*D.sqrt(y)}return h*d}function l(t,e,n,r,i,a,s,o,l){if(!(0>l||l>u(t,e,n,r,i,a,s,o))){var h,c=1,f=c/2,p=c-f,d=.01;for(h=u(t,e,n,r,i,a,s,o,p);V(h-l)>d;)f/=2,p+=(l>h?1:-1)*f,h=u(t,e,n,r,i,a,s,o,p);return p}}function h(t,e,n,r,i,a,s,o){if(!(z(t,n)z(i,s)||z(e,r)z(a,o))){var u=(t*r-e*n)*(i-s)-(t-n)*(i*o-a*s),l=(t*r-e*n)*(a-o)-(e-r)*(i*o-a*s),h=(t-n)*(a-o)-(e-r)*(i-s);if(h){var c=u/h,f=l/h,p=+c.toFixed(2),d=+f.toFixed(2);if(!(+O(t,n).toFixed(2)>p||p>+z(t,n).toFixed(2)||+O(i,s).toFixed(2)>p||p>+z(i,s).toFixed(2)||+O(e,r).toFixed(2)>d||d>+z(e,r).toFixed(2)||+O(a,o).toFixed(2)>d||d>+z(a,o).toFixed(2)))return{x:c,y:f}}}}function c(t,n,r){var i=e.bezierBBox(t),a=e.bezierBBox(n);if(!e.isBBoxIntersect(i,a))return r?0:[];for(var s=u.apply(0,t),o=u.apply(0,n),l=~~(s/5),c=~~(o/5),f=[],p=[],d={},g=r?0:[],x=0;l+1>x;x++){var v=e.findDotsAtSegment.apply(e,t.concat(x/l));f.push({x:v.x,y:v.y,t:x/l})}for(x=0;c+1>x;x++)v=e.findDotsAtSegment.apply(e,n.concat(x/c)),p.push({x:v.x,y:v.y,t:x/c});for(x=0;l>x;x++)for(var m=0;c>m;m++){var y=f[x],b=f[x+1],_=p[m],w=p[m+1],k=.001>V(b.x-y.x)?"y":"x",B=.001>V(w.x-_.x)?"y":"x",S=h(y.x,y.y,b.x,b.y,_.x,_.y,w.x,w.y);if(S){if(d[S.x.toFixed(4)]==S.y.toFixed(4))continue;d[S.x.toFixed(4)]=S.y.toFixed(4);var C=y.t+V((S[k]-y[k])/(b[k]-y[k]))*(b.t-y.t),F=_.t+V((S[B]-_[B])/(w[B]-_[B]))*(w.t-_.t);C>=0&&1>=C&&F>=0&&1>=F&&(r?g++:g.push({x:S.x,y:S.y,t1:C,t2:F}))}}return g}function f(t,n,r){t=e._path2curve(t),n=e._path2curve(n);for(var i,a,s,o,u,l,h,f,p,d,g=r?0:[],x=0,v=t.length;v>x;x++){var m=t[x];if("M"==m[0])i=u=m[1],a=l=m[2];else{"C"==m[0]?(p=[i,a].concat(m.slice(1)),i=p[6],a=p[7]):(p=[i,a,i,a,u,l,u,l],i=u,a=l);for(var y=0,b=n.length;b>y;y++){var _=n[y];if("M"==_[0])s=h=_[1],o=f=_[2];else{"C"==_[0]?(d=[s,o].concat(_.slice(1)),s=d[6],o=d[7]):(d=[s,o,s,o,h,f,h,f],s=h,o=f);var w=c(p,d,r);if(r)g+=w;else{for(var k=0,B=w.length;B>k;k++)w[k].segment1=x,w[k].segment2=y,w[k].bez1=p,w[k].bez2=d;g=g.concat(w)}}}}}return g}function p(t,e,n,r,i,a){null!=t?(this.a=+t,this.b=+e,this.c=+n,this.d=+r,this.e=+i,this.f=+a):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function d(){return this.x+E+this.y+E+this.width+" × "+this.height}function g(t,e,n,r,i,a){function s(t){return((c*t+h)*t+l)*t}function o(t,e){var n=u(t,e);return((d*n+p)*n+f)*n}function u(t,e){var n,r,i,a,o,u;for(i=t,u=0;8>u;u++){if(a=s(i)-t,e>V(a))return i;if(o=(3*c*i+2*h)*i+l,1e-6>V(o))break;i-=a/o}if(n=0,r=1,i=t,n>i)return n;if(i>r)return r;for(;r>n;){if(a=s(i),e>V(a-t))return i;t>a?n=i:r=i,i=(r-n)/2+n}return i}var l=3*e,h=3*(r-e)-l,c=1-l-h,f=3*n,p=3*(i-n)-f,d=1-f-p;return o(t,1/(200*a))}function x(t,e){var n=[],r={};if(this.ms=e,this.times=1,t){for(var i in t)t[B](i)&&(r[J(i)]=t[i],n.push(J(i)));n.sort(he)}this.anim=r,this.top=n[n.length-1],this.percents=n}function v(n,r,i,a,s,o){i=J(i);var u,l,h,c,f,d,x=n.ms,v={},m={},y={};if(a)for(w=0,k=on.length;k>w;w++){var b=on[w];if(b.el.id==r.id&&b.anim==n){b.percent!=i?(on.splice(w,1),h=1):l=b,r.attr(b.totalOrigin);break}}else a=+m;for(var w=0,k=n.percents.length;k>w;w++){if(n.percents[w]==i||n.percents[w]>a*n.top){i=n.percents[w],f=n.percents[w-1]||0,x=x/n.top*(i-f),c=n.percents[w+1],u=n.anim[i];break}a&&r.attr(n.anim[n.percents[w]])}if(u){if(l)l.initstatus=a,l.start=new Date-l.ms*a;else{for(var S in u)if(u[B](S)&&(ne[B](S)||r.paper.customAttributes[B](S)))switch(v[S]=r.attr(S),null==v[S]&&(v[S]=ee[S]),m[S]=u[S],ne[S]){case G:y[S]=(m[S]-v[S])/x;break;case"colour":v[S]=e.getRGB(v[S]);var C=e.getRGB(m[S]);y[S]={r:(C.r-v[S].r)/x,g:(C.g-v[S].g)/x,b:(C.b-v[S].b)/x};break;case"path":var F=Re(v[S],m[S]),T=F[1];for(v[S]=F[0],y[S]=[],w=0,k=v[S].length;k>w;w++){y[S][w]=[0];for(var A=1,P=v[S][w].length;P>A;A++)y[S][w][A]=(T[w][A]-v[S][w][A])/x}break;case"transform":var E=r._,R=Oe(E[S],m[S]);if(R)for(v[S]=R.from,m[S]=R.to,y[S]=[],y[S].real=!0,w=0,k=v[S].length;k>w;w++)for(y[S][w]=[v[S][w][0]],A=1,P=v[S][w].length;P>A;A++)y[S][w][A]=(m[S][w][A]-v[S][w][A])/x;else{var q=r.matrix||new p,j={_:{transform:E.transform},getBBox:function(){return r.getBBox(1)}};v[S]=[q.a,q.b,q.c,q.d,q.e,q.f],De(j,m[S]),m[S]=j._.transform,y[S]=[(j.matrix.a-q.a)/x,(j.matrix.b-q.b)/x,(j.matrix.c-q.c)/x,(j.matrix.d-q.d)/x,(j.matrix.e-q.e)/x,(j.matrix.f-q.f)/x]}break;case"csv":var D=M(u[S])[I](_),z=M(v[S])[I](_);if("clip-rect"==S)for(v[S]=z,y[S]=[],w=z.length;w--;)y[S][w]=(D[w]-v[S][w])/x;m[S]=D;break;default:for(D=[][L](u[S]),z=[][L](v[S]),y[S]=[],w=r.paper.customAttributes[S].length;w--;)y[S][w]=((D[w]||0)-(z[w]||0))/x}var O=u.easing,V=e.easing_formulas[O];if(!V)if(V=M(O).match(Z),V&&5==V.length){var X=V;V=function(t){return g(t,+X[1],+X[2],+X[3],+X[4],x)}}else V=fe;if(d=u.start||n.start||+new Date,b={anim:n,percent:i,timestamp:d,start:d+(n.del||0),status:0,initstatus:a||0,stop:!1,ms:x,easing:V,from:v,diff:y,to:m,el:r,callback:u.callback,prev:f,next:c,repeat:o||n.times,origin:r.attr(),totalOrigin:s},on.push(b),a&&!l&&!h&&(b.stop=!0,b.start=new Date-x*a,1==on.length))return ln();h&&(b.start=new Date-b.ms*a),1==on.length&&un(ln)}t("raphael.anim.start."+r.id,r,n)}}function m(t){for(var e=0;on.length>e;e++)on[e].el.paper==t&&on.splice(e--,1)}e.version="2.1.0",e.eve=t;var y,b,_=/[, ]+/,w={circle:1,rect:1,path:1,ellipse:1,text:1,image:1},k=/\{(\d+)\}/g,B="hasOwnProperty",S={doc:document,win:window},C={was:Object.prototype[B].call(S.win,"Raphael"),is:S.win.Raphael},F=function(){this.ca=this.customAttributes={}},T="apply",L="concat",A="createTouch"in S.doc,P="",E=" ",M=String,I="split",R="click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[I](E),q={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},j=M.prototype.toLowerCase,D=Math,z=D.max,O=D.min,V=D.abs,X=D.pow,Y=D.PI,G="number",N="string",W="array",$=Object.prototype.toString,H=(e._ISURL=/^url\(['"]?([^\)]+?)['"]?\)$/i,/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i),U={NaN:1,Infinity:1,"-Infinity":1},Z=/^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,Q=D.round,J=parseFloat,K=parseInt,te=M.prototype.toUpperCase,ee=e._availableAttrs={"arrow-end":"none","arrow-start":"none",blur:0,"clip-rect":"0 0 1e9 1e9",cursor:"default",cx:0,cy:0,fill:"#fff","fill-opacity":1,font:'10px "Arial"',"font-family":'"Arial"',"font-size":"10","font-style":"normal","font-weight":400,gradient:0,height:0,href:"http://raphaeljs.com/","letter-spacing":0,opacity:1,path:"M0,0",r:0,rx:0,ry:0,src:"",stroke:"#000","stroke-dasharray":"","stroke-linecap":"butt","stroke-linejoin":"butt","stroke-miterlimit":0,"stroke-opacity":1,"stroke-width":1,target:"_blank","text-anchor":"middle",title:"Raphael",transform:"",width:0,x:0,y:0},ne=e._availableAnimAttrs={blur:G,"clip-rect":"csv",cx:G,cy:G,fill:"colour","fill-opacity":G,"font-size":G,height:G,opacity:G,path:"path",r:G,rx:G,ry:G,stroke:"colour","stroke-opacity":G,"stroke-width":G,transform:"transform",width:G,x:G,y:G},re=/[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,ie={hs:1,rg:1},ae=/,?([achlmqrstvxz]),?/gi,se=/([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/gi,oe=/([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/gi,ue=/(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/gi,le=(e._radial_gradient=/^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,{}),he=function(t,e){return J(t)-J(e)},ce=function(){},fe=function(t){return t},pe=e._rectPath=function(t,e,n,r,i){return i?[["M",t+i,e],["l",n-2*i,0],["a",i,i,0,0,1,i,i],["l",0,r-2*i],["a",i,i,0,0,1,-i,i],["l",2*i-n,0],["a",i,i,0,0,1,-i,-i],["l",0,2*i-r],["a",i,i,0,0,1,i,-i],["z"]]:[["M",t,e],["l",n,0],["l",0,r],["l",-n,0],["z"]]},de=function(t,e,n,r){return null==r&&(r=n),[["M",t,e],["m",0,-r],["a",n,r,0,1,1,0,2*r],["a",n,r,0,1,1,0,-2*r],["z"]]},ge=e._getPath={path:function(t){return t.attr("path")},circle:function(t){var e=t.attrs;return de(e.cx,e.cy,e.r)},ellipse:function(t){var e=t.attrs;return de(e.cx,e.cy,e.rx,e.ry)},rect:function(t){var e=t.attrs;return pe(e.x,e.y,e.width,e.height,e.r)},image:function(t){var e=t.attrs;return pe(e.x,e.y,e.width,e.height)},text:function(t){var e=t._getBBox();return pe(e.x,e.y,e.width,e.height)},set:function(t){var e=t._getBBox();return pe(e.x,e.y,e.width,e.height)}},xe=e.mapPath=function(t,e){if(!e)return t;var n,r,i,a,s,o,u;for(t=Re(t),i=0,s=t.length;s>i;i++)for(u=t[i],a=1,o=u.length;o>a;a+=2)n=e.x(u[a],u[a+1]),r=e.y(u[a],u[a+1]),u[a]=n,u[a+1]=r;return t};if(e._g=S,e.type=S.win.SVGAngle||S.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML","VML"==e.type){var ve,me=S.doc.createElement("div");if(me.innerHTML='',ve=me.firstChild,ve.style.behavior="url(#default#VML)",!ve||"object"!=typeof ve.adj)return e.type=P;me=null}e.svg=!(e.vml="VML"==e.type),e._Paper=F,e.fn=b=F.prototype=e.prototype,e._id=0,e._oid=0,e.is=function(t,e){return e=j.call(e),"finite"==e?!U[B](+t):"array"==e?t instanceof Array:"null"==e&&null===t||e==typeof t&&null!==t||"object"==e&&t===Object(t)||"array"==e&&Array.isArray&&Array.isArray(t)||$.call(t).slice(8,-1).toLowerCase()==e},e.angle=function(t,n,r,i,a,s){if(null==a){var o=t-r,u=n-i;return o||u?(180+180*D.atan2(-u,-o)/Y+360)%360:0}return e.angle(t,n,a,s)-e.angle(r,i,a,s)},e.rad=function(t){return t%360*Y/180},e.deg=function(t){return 180*t/Y%360},e.snapTo=function(t,n,r){if(r=e.is(r,"finite")?r:10,e.is(t,W)){for(var i=t.length;i--;)if(r>=V(t[i]-n))return t[i]}else{t=+t;var a=n%t;if(r>a)return n-a;if(a>t-r)return n-a+t}return n},e.createUUID=function(t,e){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(t,e).toUpperCase()}}(/[xy]/g,function(t){var e=0|16*D.random(),n="x"==t?e:8|3&e;return n.toString(16)}),e.setWindow=function(n){t("raphael.setWindow",e,S.win,n),S.win=n,S.doc=S.win.document,e._engine.initWin&&e._engine.initWin(S.win)};var ye=function(t){if(e.vml){var n,r=/^\s+|\s+$/g;try{var a=new ActiveXObject("htmlfile");a.write(""),a.close(),n=a.body}catch(s){n=createPopup().document.body}var o=n.createTextRange();ye=i(function(t){try{n.style.color=M(t).replace(r,P);var e=o.queryCommandValue("ForeColor");return e=(255&e)<<16|65280&e|(16711680&e)>>>16,"#"+("000000"+e.toString(16)).slice(-6)}catch(i){return"none"}})}else{var u=S.doc.createElement("i");u.title="Raphaël Colour Picker",u.style.display="none",S.doc.body.appendChild(u),ye=i(function(t){return u.style.color=t,S.doc.defaultView.getComputedStyle(u,P).getPropertyValue("color")})}return ye(t)},be=function(){return"hsb("+[this.h,this.s,this.b]+")"},_e=function(){return"hsl("+[this.h,this.s,this.l]+")"},we=function(){return this.hex},ke=function(t,n,r){if(null==n&&e.is(t,"object")&&"r"in t&&"g"in t&&"b"in t&&(r=t.b,n=t.g,t=t.r),null==n&&e.is(t,N)){var i=e.getRGB(t);t=i.r,n=i.g,r=i.b}return(t>1||n>1||r>1)&&(t/=255,n/=255,r/=255),[t,n,r]},Be=function(t,n,r,i){t*=255,n*=255,r*=255;var a={r:t,g:n,b:r,hex:e.rgb(t,n,r),toString:we};return e.is(i,"finite")&&(a.opacity=i),a};e.color=function(t){var n;return e.is(t,"object")&&"h"in t&&"s"in t&&"b"in t?(n=e.hsb2rgb(t),t.r=n.r,t.g=n.g,t.b=n.b,t.hex=n.hex):e.is(t,"object")&&"h"in t&&"s"in t&&"l"in t?(n=e.hsl2rgb(t),t.r=n.r,t.g=n.g,t.b=n.b,t.hex=n.hex):(e.is(t,"string")&&(t=e.getRGB(t)),e.is(t,"object")&&"r"in t&&"g"in t&&"b"in t?(n=e.rgb2hsl(t),t.h=n.h,t.s=n.s,t.l=n.l,n=e.rgb2hsb(t),t.v=n.b):(t={hex:"none"},t.r=t.g=t.b=t.h=t.s=t.v=t.l=-1)),t.toString=we,t},e.hsb2rgb=function(t,e,n,r){this.is(t,"object")&&"h"in t&&"s"in t&&"b"in t&&(n=t.b,e=t.s,t=t.h,r=t.o),t*=360;var i,a,s,o,u;return t=t%360/60,u=n*e,o=u*(1-V(t%2-1)),i=a=s=n-u,t=~~t,i+=[u,o,0,0,o,u][t],a+=[o,u,u,o,0,0][t],s+=[0,0,o,u,u,o][t],Be(i,a,s,r)},e.hsl2rgb=function(t,e,n,r){this.is(t,"object")&&"h"in t&&"s"in t&&"l"in t&&(n=t.l,e=t.s,t=t.h),(t>1||e>1||n>1)&&(t/=360,e/=100,n/=100),t*=360;var i,a,s,o,u;return t=t%360/60,u=2*e*(.5>n?n:1-n),o=u*(1-V(t%2-1)),i=a=s=n-u/2,t=~~t,i+=[u,o,0,0,o,u][t],a+=[o,u,u,o,0,0][t],s+=[0,0,o,u,u,o][t],Be(i,a,s,r)},e.rgb2hsb=function(t,e,n){n=ke(t,e,n),t=n[0],e=n[1],n=n[2];var r,i,a,s;return a=z(t,e,n),s=a-O(t,e,n),r=0==s?null:a==t?(e-n)/s:a==e?(n-t)/s+2:(t-e)/s+4,r=60*((r+360)%6)/360,i=0==s?0:s/a,{h:r,s:i,b:a,toString:be}},e.rgb2hsl=function(t,e,n){n=ke(t,e,n),t=n[0],e=n[1],n=n[2];var r,i,a,s,o,u;return s=z(t,e,n),o=O(t,e,n),u=s-o,r=0==u?null:s==t?(e-n)/u:s==e?(n-t)/u+2:(t-e)/u+4,r=60*((r+360)%6)/360,a=(s+o)/2,i=0==u?0:.5>a?u/(2*a):u/(2-2*a),{h:r,s:i,l:a,toString:_e}},e._path2string=function(){return this.join(",").replace(ae,"$1")},e._preload=function(t,e){var n=S.doc.createElement("img");n.style.cssText="position:absolute;left:-9999em;top:-9999em",n.onload=function(){e.call(this),this.onload=null,S.doc.body.removeChild(this)},n.onerror=function(){S.doc.body.removeChild(this)},S.doc.body.appendChild(n),n.src=t},e.getRGB=i(function(t){if(!t||(t=M(t)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:a};if("none"==t)return{r:-1,g:-1,b:-1,hex:"none",toString:a};!(ie[B](t.toLowerCase().substring(0,2))||"#"==t.charAt())&&(t=ye(t));var n,r,i,s,o,u,l=t.match(H);return l?(l[2]&&(i=K(l[2].substring(5),16),r=K(l[2].substring(3,5),16),n=K(l[2].substring(1,3),16)),l[3]&&(i=K((o=l[3].charAt(3))+o,16),r=K((o=l[3].charAt(2))+o,16),n=K((o=l[3].charAt(1))+o,16)),l[4]&&(u=l[4][I](re),n=J(u[0]),"%"==u[0].slice(-1)&&(n*=2.55),r=J(u[1]),"%"==u[1].slice(-1)&&(r*=2.55),i=J(u[2]),"%"==u[2].slice(-1)&&(i*=2.55),"rgba"==l[1].toLowerCase().slice(0,4)&&(s=J(u[3])),u[3]&&"%"==u[3].slice(-1)&&(s/=100)),l[5]?(u=l[5][I](re),n=J(u[0]),"%"==u[0].slice(-1)&&(n*=2.55),r=J(u[1]),"%"==u[1].slice(-1)&&(r*=2.55),i=J(u[2]),"%"==u[2].slice(-1)&&(i*=2.55),("deg"==u[0].slice(-3)||"°"==u[0].slice(-1))&&(n/=360),"hsba"==l[1].toLowerCase().slice(0,4)&&(s=J(u[3])),u[3]&&"%"==u[3].slice(-1)&&(s/=100),e.hsb2rgb(n,r,i,s)):l[6]?(u=l[6][I](re),n=J(u[0]),"%"==u[0].slice(-1)&&(n*=2.55),r=J(u[1]),"%"==u[1].slice(-1)&&(r*=2.55),i=J(u[2]),"%"==u[2].slice(-1)&&(i*=2.55),("deg"==u[0].slice(-3)||"°"==u[0].slice(-1))&&(n/=360),"hsla"==l[1].toLowerCase().slice(0,4)&&(s=J(u[3])),u[3]&&"%"==u[3].slice(-1)&&(s/=100),e.hsl2rgb(n,r,i,s)):(l={r:n,g:r,b:i,toString:a},l.hex="#"+(16777216|i|r<<8|n<<16).toString(16).slice(1),e.is(s,"finite")&&(l.opacity=s),l)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:a}},e),e.hsb=i(function(t,n,r){return e.hsb2rgb(t,n,r).hex}),e.hsl=i(function(t,n,r){return e.hsl2rgb(t,n,r).hex}),e.rgb=i(function(t,e,n){return"#"+(16777216|n|e<<8|t<<16).toString(16).slice(1)}),e.getColor=function(t){var e=this.getColor.start=this.getColor.start||{h:0,s:1,b:t||.75},n=this.hsb2rgb(e.h,e.s,e.b);return e.h+=.075,e.h>1&&(e.h=0,e.s-=.2,0>=e.s&&(this.getColor.start={h:0,s:1,b:e.b})),n.hex},e.getColor.reset=function(){delete this.start},e.parsePathString=function(t){if(!t)return null;var n=Se(t);if(n.arr)return Fe(n.arr);var r={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},i=[];return e.is(t,W)&&e.is(t[0],W)&&(i=Fe(t)),i.length||M(t).replace(se,function(t,e,n){var a=[],s=e.toLowerCase();if(n.replace(ue,function(t,e){e&&a.push(+e)}),"m"==s&&a.length>2&&(i.push([e][L](a.splice(0,2))),s="l",e="m"==e?"l":"L"),"r"==s)i.push([e][L](a));else for(;a.length>=r[s]&&(i.push([e][L](a.splice(0,r[s]))),r[s]););}),i.toString=e._path2string,n.arr=Fe(i),i},e.parseTransformString=i(function(t){if(!t)return null;var n=[];return e.is(t,W)&&e.is(t[0],W)&&(n=Fe(t)),n.length||M(t).replace(oe,function(t,e,r){var i=[];j.call(e),r.replace(ue,function(t,e){e&&i.push(+e)}),n.push([e][L](i))}),n.toString=e._path2string,n});var Se=function(t){var e=Se.ps=Se.ps||{};return e[t]?e[t].sleep=100:e[t]={sleep:100},setTimeout(function(){for(var n in e)e[B](n)&&n!=t&&(e[n].sleep--,!e[n].sleep&&delete e[n])}),e[t]};e.findDotsAtSegment=function(t,e,n,r,i,a,s,o,u){var l=1-u,h=X(l,3),c=X(l,2),f=u*u,p=f*u,d=h*t+3*c*u*n+3*l*u*u*i+p*s,g=h*e+3*c*u*r+3*l*u*u*a+p*o,x=t+2*u*(n-t)+f*(i-2*n+t),v=e+2*u*(r-e)+f*(a-2*r+e),m=n+2*u*(i-n)+f*(s-2*i+n),y=r+2*u*(a-r)+f*(o-2*a+r),b=l*t+u*n,_=l*e+u*r,w=l*i+u*s,k=l*a+u*o,B=90-180*D.atan2(x-m,v-y)/Y;return(x>m||y>v)&&(B+=180),{x:d,y:g,m:{x:x,y:v},n:{x:m,y:y},start:{x:b,y:_},end:{x:w,y:k},alpha:B}},e.bezierBBox=function(t,n,r,i,a,s,o,u){e.is(t,"array")||(t=[t,n,r,i,a,s,o,u]);var l=Ie.apply(null,t);return{x:l.min.x,y:l.min.y,x2:l.max.x,y2:l.max.y,width:l.max.x-l.min.x,height:l.max.y-l.min.y}},e.isPointInsideBBox=function(t,e,n){return e>=t.x&&t.x2>=e&&n>=t.y&&t.y2>=n},e.isBBoxIntersect=function(t,n){var r=e.isPointInsideBBox;return r(n,t.x,t.y)||r(n,t.x2,t.y)||r(n,t.x,t.y2)||r(n,t.x2,t.y2)||r(t,n.x,n.y)||r(t,n.x2,n.y)||r(t,n.x,n.y2)||r(t,n.x2,n.y2)||(t.xn.x||n.xt.x)&&(t.yn.y||n.yt.y)},e.pathIntersection=function(t,e){return f(t,e)},e.pathIntersectionNumber=function(t,e){return f(t,e,1)},e.isPointInsidePath=function(t,n,r){var i=e.pathBBox(t);return e.isPointInsideBBox(i,n,r)&&1==f(t,[["M",n,r],["H",i.x2+10]],1)%2},e._removedFactory=function(e){return function(){t("raphael.log",null,"Raphaël: you are calling to method “"+e+"” of removed object",e)}};var Ce=e.pathBBox=function(t){var e=Se(t);if(e.bbox)return n(e.bbox);if(!t)return{x:0,y:0,width:0,height:0,x2:0,y2:0};t=Re(t);for(var r,i=0,a=0,s=[],o=[],u=0,l=t.length;l>u;u++)if(r=t[u],"M"==r[0])i=r[1],a=r[2],s.push(i),o.push(a);else{var h=Ie(i,a,r[1],r[2],r[3],r[4],r[5],r[6]);s=s[L](h.min.x,h.max.x),o=o[L](h.min.y,h.max.y),i=r[5],a=r[6]}var c=O[T](0,s),f=O[T](0,o),p=z[T](0,s),d=z[T](0,o),g=p-c,x=d-f,v={x:c,y:f,x2:p,y2:d,width:g,height:x,cx:c+g/2,cy:f+x/2};return e.bbox=n(v),v},Fe=function(t){var r=n(t);return r.toString=e._path2string,r},Te=e._pathToRelative=function(t){var n=Se(t);if(n.rel)return Fe(n.rel);e.is(t,W)&&e.is(t&&t[0],W)||(t=e.parsePathString(t));var r=[],i=0,a=0,s=0,o=0,u=0;"M"==t[0][0]&&(i=t[0][1],a=t[0][2],s=i,o=a,u++,r.push(["M",i,a]));for(var l=u,h=t.length;h>l;l++){var c=r[l]=[],f=t[l];if(f[0]!=j.call(f[0]))switch(c[0]=j.call(f[0]),c[0]){case"a":c[1]=f[1],c[2]=f[2],c[3]=f[3],c[4]=f[4],c[5]=f[5],c[6]=+(f[6]-i).toFixed(3),c[7]=+(f[7]-a).toFixed(3);break;case"v":c[1]=+(f[1]-a).toFixed(3);break;case"m":s=f[1],o=f[2];default:for(var p=1,d=f.length;d>p;p++)c[p]=+(f[p]-(p%2?i:a)).toFixed(3)}else{c=r[l]=[],"m"==f[0]&&(s=f[1]+i,o=f[2]+a);for(var g=0,x=f.length;x>g;g++)r[l][g]=f[g]}var v=r[l].length;switch(r[l][0]){case"z":i=s,a=o;break;case"h":i+=+r[l][v-1];break;case"v":a+=+r[l][v-1];break;default:i+=+r[l][v-2],a+=+r[l][v-1]}}return r.toString=e._path2string,n.rel=Fe(r),r},Le=e._pathToAbsolute=function(t){var n=Se(t);if(n.abs)return Fe(n.abs);if(e.is(t,W)&&e.is(t&&t[0],W)||(t=e.parsePathString(t)),!t||!t.length)return[["M",0,0]];var r=[],i=0,a=0,o=0,u=0,l=0;"M"==t[0][0]&&(i=+t[0][1],a=+t[0][2],o=i,u=a,l++,r[0]=["M",i,a]);for(var h,c,f=3==t.length&&"M"==t[0][0]&&"R"==t[1][0].toUpperCase()&&"Z"==t[2][0].toUpperCase(),p=l,d=t.length;d>p;p++){if(r.push(h=[]),c=t[p],c[0]!=te.call(c[0]))switch(h[0]=te.call(c[0]),h[0]){case"A":h[1]=c[1],h[2]=c[2],h[3]=c[3],h[4]=c[4],h[5]=c[5],h[6]=+(c[6]+i),h[7]=+(c[7]+a);break;case"V":h[1]=+c[1]+a;break;case"H":h[1]=+c[1]+i;break;case"R":for(var g=[i,a][L](c.slice(1)),x=2,v=g.length;v>x;x++)g[x]=+g[x]+i,g[++x]=+g[x]+a;r.pop(),r=r[L](s(g,f));break;case"M":o=+c[1]+i,u=+c[2]+a;default:for(x=1,v=c.length;v>x;x++)h[x]=+c[x]+(x%2?i:a)}else if("R"==c[0])g=[i,a][L](c.slice(1)),r.pop(),r=r[L](s(g,f)),h=["R"][L](c.slice(-2));else for(var m=0,y=c.length;y>m;m++)h[m]=c[m];switch(h[0]){case"Z":i=o,a=u;break;case"H":i=h[1];break;case"V":a=h[1];break;case"M":o=h[h.length-2],u=h[h.length-1];default:i=h[h.length-2],a=h[h.length-1]}}return r.toString=e._path2string,n.abs=Fe(r),r},Ae=function(t,e,n,r){return[t,e,n,r,n,r]},Pe=function(t,e,n,r,i,a){var s=1/3,o=2/3;return[s*t+o*n,s*e+o*r,s*i+o*n,s*a+o*r,i,a]},Ee=function(t,e,n,r,a,s,o,u,l,h){var c,f=120*Y/180,p=Y/180*(+a||0),d=[],g=i(function(t,e,n){var r=t*D.cos(n)-e*D.sin(n),i=t*D.sin(n)+e*D.cos(n);return{x:r,y:i}});if(h)B=h[0],S=h[1],w=h[2],k=h[3];else{c=g(t,e,-p),t=c.x,e=c.y,c=g(u,l,-p),u=c.x,l=c.y;var x=(D.cos(Y/180*a),D.sin(Y/180*a),(t-u)/2),v=(e-l)/2,m=x*x/(n*n)+v*v/(r*r);m>1&&(m=D.sqrt(m),n=m*n,r=m*r);var y=n*n,b=r*r,_=(s==o?-1:1)*D.sqrt(V((y*b-y*v*v-b*x*x)/(y*v*v+b*x*x))),w=_*n*v/r+(t+u)/2,k=_*-r*x/n+(e+l)/2,B=D.asin(((e-k)/r).toFixed(9)),S=D.asin(((l-k)/r).toFixed(9));B=w>t?Y-B:B,S=w>u?Y-S:S,0>B&&(B=2*Y+B),0>S&&(S=2*Y+S),o&&B>S&&(B-=2*Y),!o&&S>B&&(S-=2*Y)}var C=S-B;if(V(C)>f){var F=S,T=u,A=l;S=B+f*(o&&S>B?1:-1),u=w+n*D.cos(S),l=k+r*D.sin(S),d=Ee(u,l,n,r,a,0,o,T,A,[S,F,w,k])}C=S-B;var P=D.cos(B),E=D.sin(B),M=D.cos(S),R=D.sin(S),q=D.tan(C/4),j=4/3*n*q,z=4/3*r*q,O=[t,e],X=[t+j*E,e-z*P],G=[u+j*R,l-z*M],N=[u,l];if(X[0]=2*O[0]-X[0],X[1]=2*O[1]-X[1],h)return[X,G,N][L](d);d=[X,G,N][L](d).join()[I](",");for(var W=[],$=0,H=d.length;H>$;$++)W[$]=$%2?g(d[$-1],d[$],p).y:g(d[$],d[$+1],p).x;return W},Me=function(t,e,n,r,i,a,s,o,u){var l=1-u;return{x:X(l,3)*t+3*X(l,2)*u*n+3*l*u*u*i+X(u,3)*s,y:X(l,3)*e+3*X(l,2)*u*r+3*l*u*u*a+X(u,3)*o}},Ie=i(function(t,e,n,r,i,a,s,o){var u,l=i-2*n+t-(s-2*i+n),h=2*(n-t)-2*(i-n),c=t-n,f=(-h+D.sqrt(h*h-4*l*c))/2/l,p=(-h-D.sqrt(h*h-4*l*c))/2/l,d=[e,o],g=[t,s];return V(f)>"1e12"&&(f=.5),V(p)>"1e12"&&(p=.5),f>0&&1>f&&(u=Me(t,e,n,r,i,a,s,o,f),g.push(u.x),d.push(u.y)),p>0&&1>p&&(u=Me(t,e,n,r,i,a,s,o,p),g.push(u.x),d.push(u.y)),l=a-2*r+e-(o-2*a+r),h=2*(r-e)-2*(a-r),c=e-r,f=(-h+D.sqrt(h*h-4*l*c))/2/l,p=(-h-D.sqrt(h*h-4*l*c))/2/l,V(f)>"1e12"&&(f=.5),V(p)>"1e12"&&(p=.5),f>0&&1>f&&(u=Me(t,e,n,r,i,a,s,o,f),g.push(u.x),d.push(u.y)),p>0&&1>p&&(u=Me(t,e,n,r,i,a,s,o,p),g.push(u.x),d.push(u.y)),{min:{x:O[T](0,g),y:O[T](0,d)},max:{x:z[T](0,g),y:z[T](0,d)}}}),Re=e._path2curve=i(function(t,e){var n=!e&&Se(t);if(!e&&n.curve)return Fe(n.curve);for(var r=Le(t),i=e&&Le(e),a={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},s={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},o=(function(t,e){var n,r;if(!t)return["C",e.x,e.y,e.x,e.y,e.x,e.y];switch(!(t[0]in{T:1,Q:1})&&(e.qx=e.qy=null),t[0]){case"M":e.X=t[1],e.Y=t[2];break;case"A":t=["C"][L](Ee[T](0,[e.x,e.y][L](t.slice(1))));break;case"S":n=e.x+(e.x-(e.bx||e.x)),r=e.y+(e.y-(e.by||e.y)),t=["C",n,r][L](t.slice(1));break;case"T":e.qx=e.x+(e.x-(e.qx||e.x)),e.qy=e.y+(e.y-(e.qy||e.y)),t=["C"][L](Pe(e.x,e.y,e.qx,e.qy,t[1],t[2]));break;case"Q":e.qx=t[1],e.qy=t[2],t=["C"][L](Pe(e.x,e.y,t[1],t[2],t[3],t[4]));break;case"L":t=["C"][L](Ae(e.x,e.y,t[1],t[2]));break;case"H":t=["C"][L](Ae(e.x,e.y,t[1],e.y));break;case"V":t=["C"][L](Ae(e.x,e.y,e.x,t[1]));break;case"Z":t=["C"][L](Ae(e.x,e.y,e.X,e.Y))}return t}),u=function(t,e){if(t[e].length>7){t[e].shift();for(var n=t[e];n.length;)t.splice(e++,0,["C"][L](n.splice(0,6)));t.splice(e,1),c=z(r.length,i&&i.length||0)}},l=function(t,e,n,a,s){t&&e&&"M"==t[s][0]&&"M"!=e[s][0]&&(e.splice(s,0,["M",a.x,a.y]),n.bx=0,n.by=0,n.x=t[s][1],n.y=t[s][2],c=z(r.length,i&&i.length||0))},h=0,c=z(r.length,i&&i.length||0);c>h;h++){r[h]=o(r[h],a),u(r,h),i&&(i[h]=o(i[h],s)),i&&u(i,h),l(r,i,a,s,h),l(i,r,s,a,h);var f=r[h],p=i&&i[h],d=f.length,g=i&&p.length;a.x=f[d-2],a.y=f[d-1],a.bx=J(f[d-4])||a.x,a.by=J(f[d-3])||a.y,s.bx=i&&(J(p[g-4])||s.x),s.by=i&&(J(p[g-3])||s.y),s.x=i&&p[g-2],s.y=i&&p[g-1]}return i||(n.curve=Fe(r)),i?[r,i]:r},null,Fe),qe=(e._parseDots=i(function(t){for(var n=[],r=0,i=t.length;i>r;r++){var a={},s=t[r].match(/^([^:]*):?([\d\.]*)/);if(a.color=e.getRGB(s[1]),a.color.error)return null;a.color=a.color.hex,s[2]&&(a.offset=s[2]+"%"),n.push(a)}for(r=1,i=n.length-1;i>r;r++)if(!n[r].offset){for(var o=J(n[r-1].offset||0),u=0,l=r+1;i>l;l++)if(n[l].offset){u=n[l].offset;break}u||(u=100,l=i),u=J(u);for(var h=(u-o)/(l-r+1);l>r;r++)o+=h,n[r].offset=o+"%"}return n}),e._tear=function(t,e){t==e.top&&(e.top=t.prev),t==e.bottom&&(e.bottom=t.next),t.next&&(t.next.prev=t.prev),t.prev&&(t.prev.next=t.next)}),je=(e._tofront=function(t,e){e.top!==t&&(qe(t,e),t.next=null,t.prev=e.top,e.top.next=t,e.top=t)},e._toback=function(t,e){e.bottom!==t&&(qe(t,e),t.next=e.bottom,t.prev=null,e.bottom.prev=t,e.bottom=t)},e._insertafter=function(t,e,n){qe(t,n),e==n.top&&(n.top=t),e.next&&(e.next.prev=t),t.next=e.next,t.prev=e,e.next=t},e._insertbefore=function(t,e,n){qe(t,n),e==n.bottom&&(n.bottom=t),e.prev&&(e.prev.next=t),t.prev=e.prev,e.prev=t,t.next=e},e.toMatrix=function(t,e){var n=Ce(t),r={_:{transform:P},getBBox:function(){return n}};return De(r,e),r.matrix}),De=(e.transformPath=function(t,e){return xe(t,je(t,e))},e._extractTransform=function(t,n){if(null==n)return t._.transform;n=M(n).replace(/\.{3}|\u2026/g,t._.transform||P);var r=e.parseTransformString(n),i=0,a=0,s=0,o=1,u=1,l=t._,h=new p;if(l.transform=r||[],r)for(var c=0,f=r.length;f>c;c++){var d,g,x,v,m,y=r[c],b=y.length,_=M(y[0]).toLowerCase(),w=y[0]!=_,k=w?h.invert():0;"t"==_&&3==b?w?(d=k.x(0,0),g=k.y(0,0),x=k.x(y[1],y[2]),v=k.y(y[1],y[2]),h.translate(x-d,v-g)):h.translate(y[1],y[2]):"r"==_?2==b?(m=m||t.getBBox(1),h.rotate(y[1],m.x+m.width/2,m.y+m.height/2),i+=y[1]):4==b&&(w?(x=k.x(y[2],y[3]),v=k.y(y[2],y[3]),h.rotate(y[1],x,v)):h.rotate(y[1],y[2],y[3]),i+=y[1]):"s"==_?2==b||3==b?(m=m||t.getBBox(1),h.scale(y[1],y[b-1],m.x+m.width/2,m.y+m.height/2),o*=y[1],u*=y[b-1]):5==b&&(w?(x=k.x(y[3],y[4]),v=k.y(y[3],y[4]),h.scale(y[1],y[2],x,v)):h.scale(y[1],y[2],y[3],y[4]),o*=y[1],u*=y[2]):"m"==_&&7==b&&h.add(y[1],y[2],y[3],y[4],y[5],y[6]),l.dirtyT=1,t.matrix=h}t.matrix=h,l.sx=o,l.sy=u,l.deg=i,l.dx=a=h.e,l.dy=s=h.f,1==o&&1==u&&!i&&l.bbox?(l.bbox.x+=+a,l.bbox.y+=+s):l.dirtyT=1}),ze=function(t){var e=t[0];switch(e.toLowerCase()){case"t":return[e,0,0];case"m":return[e,1,0,0,1,0,0];case"r":return 4==t.length?[e,0,t[2],t[3]]:[e,0];case"s":return 5==t.length?[e,1,1,t[3],t[4]]:3==t.length?[e,1,1]:[e,1]}},Oe=e._equaliseTransform=function(t,n){n=M(n).replace(/\.{3}|\u2026/g,t),t=e.parseTransformString(t)||[],n=e.parseTransformString(n)||[];for(var r,i,a,s,o=z(t.length,n.length),u=[],l=[],h=0;o>h;h++){if(a=t[h]||ze(n[h]),s=n[h]||ze(a),a[0]!=s[0]||"r"==a[0].toLowerCase()&&(a[2]!=s[2]||a[3]!=s[3])||"s"==a[0].toLowerCase()&&(a[3]!=s[3]||a[4]!=s[4]))return;for(u[h]=[],l[h]=[],r=0,i=z(a.length,s.length);i>r;r++)r in a&&(u[h][r]=a[r]),r in s&&(l[h][r]=s[r])}return{from:u,to:l}};e._getContainer=function(t,n,r,i){var a;return a=null!=i||e.is(t,"object")?t:S.doc.getElementById(t),null!=a?a.tagName?null==n?{container:a,width:a.style.pixelWidth||a.offsetWidth,height:a.style.pixelHeight||a.offsetHeight}:{container:a,width:n,height:r}:{container:1,x:t,y:n,width:r,height:i}:void 0},e.pathToRelative=Te,e._engine={},e.path2curve=Re,e.matrix=function(t,e,n,r,i,a){return new p(t,e,n,r,i,a)},function(t){function n(t){return t[0]*t[0]+t[1]*t[1]}function r(t){var e=D.sqrt(n(t));t[0]&&(t[0]/=e),t[1]&&(t[1]/=e)}t.add=function(t,e,n,r,i,a){var s,o,u,l,h=[[],[],[]],c=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],f=[[t,n,i],[e,r,a],[0,0,1]];for(t&&t instanceof p&&(f=[[t.a,t.c,t.e],[t.b,t.d,t.f],[0,0,1]]),s=0;3>s;s++)for(o=0;3>o;o++){for(l=0,u=0;3>u;u++)l+=c[s][u]*f[u][o];h[s][o]=l}this.a=h[0][0],this.b=h[1][0],this.c=h[0][1],this.d=h[1][1],this.e=h[0][2],this.f=h[1][2]},t.invert=function(){var t=this,e=t.a*t.d-t.b*t.c;return new p(t.d/e,-t.b/e,-t.c/e,t.a/e,(t.c*t.f-t.d*t.e)/e,(t.b*t.e-t.a*t.f)/e)},t.clone=function(){return new p(this.a,this.b,this.c,this.d,this.e,this.f)},t.translate=function(t,e){this.add(1,0,0,1,t,e)},t.scale=function(t,e,n,r){null==e&&(e=t),(n||r)&&this.add(1,0,0,1,n,r),this.add(t,0,0,e,0,0),(n||r)&&this.add(1,0,0,1,-n,-r)},t.rotate=function(t,n,r){t=e.rad(t),n=n||0,r=r||0;var i=+D.cos(t).toFixed(9),a=+D.sin(t).toFixed(9);this.add(i,a,-a,i,n,r),this.add(1,0,0,1,-n,-r)},t.x=function(t,e){return t*this.a+e*this.c+this.e},t.y=function(t,e){return t*this.b+e*this.d+this.f},t.get=function(t){return+this[M.fromCharCode(97+t)].toFixed(4)},t.toString=function(){return e.svg?"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")":[this.get(0),this.get(2),this.get(1),this.get(3),0,0].join()},t.toFilter=function(){return"progid:DXImageTransform.Microsoft.Matrix(M11="+this.get(0)+", M12="+this.get(2)+", M21="+this.get(1)+", M22="+this.get(3)+", Dx="+this.get(4)+", Dy="+this.get(5)+", sizingmethod='auto expand')"},t.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},t.split=function(){var t={};t.dx=this.e,t.dy=this.f;var i=[[this.a,this.c],[this.b,this.d]];t.scalex=D.sqrt(n(i[0])),r(i[0]),t.shear=i[0][0]*i[1][0]+i[0][1]*i[1][1],i[1]=[i[1][0]-i[0][0]*t.shear,i[1][1]-i[0][1]*t.shear],t.scaley=D.sqrt(n(i[1])),r(i[1]),t.shear/=t.scaley;var a=-i[0][1],s=i[1][1];return 0>s?(t.rotate=e.deg(D.acos(s)),0>a&&(t.rotate=360-t.rotate)):t.rotate=e.deg(D.asin(a)),t.isSimple=!(+t.shear.toFixed(9)||t.scalex.toFixed(9)!=t.scaley.toFixed(9)&&t.rotate),t.isSuperSimple=!+t.shear.toFixed(9)&&t.scalex.toFixed(9)==t.scaley.toFixed(9)&&!t.rotate,t.noRotation=!+t.shear.toFixed(9)&&!t.rotate,t +},t.toTransformString=function(t){var e=t||this[I]();return e.isSimple?(e.scalex=+e.scalex.toFixed(4),e.scaley=+e.scaley.toFixed(4),e.rotate=+e.rotate.toFixed(4),(e.dx||e.dy?"t"+[e.dx,e.dy]:P)+(1!=e.scalex||1!=e.scaley?"s"+[e.scalex,e.scaley,0,0]:P)+(e.rotate?"r"+[e.rotate,0,0]:P)):"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]}}(p.prototype);var Ve=navigator.userAgent.match(/Version\/(.*?)\s/)||navigator.userAgent.match(/Chrome\/(\d+)/);b.safari="Apple Computer, Inc."==navigator.vendor&&(Ve&&4>Ve[1]||"iP"==navigator.platform.slice(0,2))||"Google Inc."==navigator.vendor&&Ve&&8>Ve[1]?function(){var t=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});setTimeout(function(){t.remove()})}:ce;for(var Xe=function(){this.returnValue=!1},Ye=function(){return this.originalEvent.preventDefault()},Ge=function(){this.cancelBubble=!0},Ne=function(){return this.originalEvent.stopPropagation()},We=function(){return S.doc.addEventListener?function(t,e,n,r){var i=A&&q[e]?q[e]:e,a=function(i){var a=S.doc.documentElement.scrollTop||S.doc.body.scrollTop,s=S.doc.documentElement.scrollLeft||S.doc.body.scrollLeft,o=i.clientX+s,u=i.clientY+a;if(A&&q[B](e))for(var l=0,h=i.targetTouches&&i.targetTouches.length;h>l;l++)if(i.targetTouches[l].target==t){var c=i;i=i.targetTouches[l],i.originalEvent=c,i.preventDefault=Ye,i.stopPropagation=Ne;break}return n.call(r,i,o,u)};return t.addEventListener(i,a,!1),function(){return t.removeEventListener(i,a,!1),!0}}:S.doc.attachEvent?function(t,e,n,r){var i=function(t){t=t||S.win.event;var e=S.doc.documentElement.scrollTop||S.doc.body.scrollTop,i=S.doc.documentElement.scrollLeft||S.doc.body.scrollLeft,a=t.clientX+i,s=t.clientY+e;return t.preventDefault=t.preventDefault||Xe,t.stopPropagation=t.stopPropagation||Ge,n.call(r,t,a,s)};t.attachEvent("on"+e,i);var a=function(){return t.detachEvent("on"+e,i),!0};return a}:void 0}(),$e=[],He=function(e){for(var n,r=e.clientX,i=e.clientY,a=S.doc.documentElement.scrollTop||S.doc.body.scrollTop,s=S.doc.documentElement.scrollLeft||S.doc.body.scrollLeft,o=$e.length;o--;){if(n=$e[o],A){for(var u,l=e.touches.length;l--;)if(u=e.touches[l],u.identifier==n.el._drag.id){r=u.clientX,i=u.clientY,(e.originalEvent?e.originalEvent:e).preventDefault();break}}else e.preventDefault();var h,c=n.el.node,f=c.nextSibling,p=c.parentNode,d=c.style.display;S.win.opera&&p.removeChild(c),c.style.display="none",h=n.el.paper.getElementByPoint(r,i),c.style.display=d,S.win.opera&&(f?p.insertBefore(c,f):p.appendChild(c)),h&&t("raphael.drag.over."+n.el.id,n.el,h),r+=s,i+=a,t("raphael.drag.move."+n.el.id,n.move_scope||n.el,r-n.el._drag.x,i-n.el._drag.y,r,i,e)}},Ue=function(n){e.unmousemove(He).unmouseup(Ue);for(var r,i=$e.length;i--;)r=$e[i],r.el._drag={},t("raphael.drag.end."+r.el.id,r.end_scope||r.start_scope||r.move_scope||r.el,n);$e=[]},Ze=e.el={},Qe=R.length;Qe--;)(function(t){e[t]=Ze[t]=function(n,r){return e.is(n,"function")&&(this.events=this.events||[],this.events.push({name:t,f:n,unbind:We(this.shape||this.node||S.doc,t,n,r||this)})),this},e["un"+t]=Ze["un"+t]=function(e){for(var n=this.events||[],r=n.length;r--;)if(n[r].name==t&&n[r].f==e)return n[r].unbind(),n.splice(r,1),!n.length&&delete this.events,this;return this}})(R[Qe]);Ze.data=function(n,r){var i=le[this.id]=le[this.id]||{};if(1==arguments.length){if(e.is(n,"object")){for(var a in n)n[B](a)&&this.data(a,n[a]);return this}return t("raphael.data.get."+this.id,this,i[n],n),i[n]}return i[n]=r,t("raphael.data.set."+this.id,this,r,n),this},Ze.removeData=function(t){return null==t?le[this.id]={}:le[this.id]&&delete le[this.id][t],this},Ze.getData=function(){return n(le[this.id]||{})},Ze.hover=function(t,e,n,r){return this.mouseover(t,n).mouseout(e,r||n)},Ze.unhover=function(t,e){return this.unmouseover(t).unmouseout(e)};var Je=[];Ze.drag=function(n,r,i,a,s,o){function u(u){(u.originalEvent||u).preventDefault();var l=S.doc.documentElement.scrollTop||S.doc.body.scrollTop,h=S.doc.documentElement.scrollLeft||S.doc.body.scrollLeft;this._drag.x=u.clientX+h,this._drag.y=u.clientY+l,this._drag.id=u.identifier,!$e.length&&e.mousemove(He).mouseup(Ue),$e.push({el:this,move_scope:a,start_scope:s,end_scope:o}),r&&t.on("raphael.drag.start."+this.id,r),n&&t.on("raphael.drag.move."+this.id,n),i&&t.on("raphael.drag.end."+this.id,i),t("raphael.drag.start."+this.id,s||a||this,u.clientX+h,u.clientY+l,u)}return this._drag={},Je.push({el:this,start:u}),this.mousedown(u),this},Ze.onDragOver=function(e){e?t.on("raphael.drag.over."+this.id,e):t.unbind("raphael.drag.over."+this.id)},Ze.undrag=function(){for(var n=Je.length;n--;)Je[n].el==this&&(this.unmousedown(Je[n].start),Je.splice(n,1),t.unbind("raphael.drag.*."+this.id));!Je.length&&e.unmousemove(He).unmouseup(Ue),$e=[]},b.circle=function(t,n,r){var i=e._engine.circle(this,t||0,n||0,r||0);return this.__set__&&this.__set__.push(i),i},b.rect=function(t,n,r,i,a){var s=e._engine.rect(this,t||0,n||0,r||0,i||0,a||0);return this.__set__&&this.__set__.push(s),s},b.ellipse=function(t,n,r,i){var a=e._engine.ellipse(this,t||0,n||0,r||0,i||0);return this.__set__&&this.__set__.push(a),a},b.path=function(t){t&&!e.is(t,N)&&!e.is(t[0],W)&&(t+=P);var n=e._engine.path(e.format[T](e,arguments),this);return this.__set__&&this.__set__.push(n),n},b.image=function(t,n,r,i,a){var s=e._engine.image(this,t||"about:blank",n||0,r||0,i||0,a||0);return this.__set__&&this.__set__.push(s),s},b.text=function(t,n,r){var i=e._engine.text(this,t||0,n||0,M(r));return this.__set__&&this.__set__.push(i),i},b.set=function(t){!e.is(t,"array")&&(t=Array.prototype.splice.call(arguments,0,arguments.length));var n=new cn(t);return this.__set__&&this.__set__.push(n),n.paper=this,n.type="set",n},b.setStart=function(t){this.__set__=t||this.set()},b.setFinish=function(){var t=this.__set__;return delete this.__set__,t},b.setSize=function(t,n){return e._engine.setSize.call(this,t,n)},b.setViewBox=function(t,n,r,i,a){return e._engine.setViewBox.call(this,t,n,r,i,a)},b.top=b.bottom=null,b.raphael=e;var Ke=function(t){var e=t.getBoundingClientRect(),n=t.ownerDocument,r=n.body,i=n.documentElement,a=i.clientTop||r.clientTop||0,s=i.clientLeft||r.clientLeft||0,o=e.top+(S.win.pageYOffset||i.scrollTop||r.scrollTop)-a,u=e.left+(S.win.pageXOffset||i.scrollLeft||r.scrollLeft)-s;return{y:o,x:u}};b.getElementByPoint=function(t,e){var n=this,r=n.canvas,i=S.doc.elementFromPoint(t,e);if(S.win.opera&&"svg"==i.tagName){var a=Ke(r),s=r.createSVGRect();s.x=t-a.x,s.y=e-a.y,s.width=s.height=1;var o=r.getIntersectionList(s,null);o.length&&(i=o[o.length-1])}if(!i)return null;for(;i.parentNode&&i!=r.parentNode&&!i.raphael;)i=i.parentNode;return i==n.canvas.parentNode&&(i=r),i=i&&i.raphael?n.getById(i.raphaelid):null},b.getElementsByBBox=function(t){var n=this.set();return this.forEach(function(r){e.isBBoxIntersect(r.getBBox(),t)&&n.push(r)}),n},b.getById=function(t){for(var e=this.bottom;e;){if(e.id==t)return e;e=e.next}return null},b.forEach=function(t,e){for(var n=this.bottom;n;){if(t.call(e,n)===!1)return this;n=n.next}return this},b.getElementsByPoint=function(t,e){var n=this.set();return this.forEach(function(r){r.isPointInside(t,e)&&n.push(r)}),n},Ze.isPointInside=function(t,n){var r=this.realPath=this.realPath||ge[this.type](this);return e.isPointInsidePath(r,t,n)},Ze.getBBox=function(t){if(this.removed)return{};var e=this._;return t?((e.dirty||!e.bboxwt)&&(this.realPath=ge[this.type](this),e.bboxwt=Ce(this.realPath),e.bboxwt.toString=d,e.dirty=0),e.bboxwt):((e.dirty||e.dirtyT||!e.bbox)&&((e.dirty||!this.realPath)&&(e.bboxwt=0,this.realPath=ge[this.type](this)),e.bbox=Ce(xe(this.realPath,this.matrix)),e.bbox.toString=d,e.dirty=e.dirtyT=0),e.bbox)},Ze.clone=function(){if(this.removed)return null;var t=this.paper[this.type]().attr(this.attr());return this.__set__&&this.__set__.push(t),t},Ze.glow=function(t){if("text"==this.type)return null;t=t||{};var e={width:(t.width||10)+(+this.attr("stroke-width")||1),fill:t.fill||!1,opacity:t.opacity||.5,offsetx:t.offsetx||0,offsety:t.offsety||0,color:t.color||"#000"},n=e.width/2,r=this.paper,i=r.set(),a=this.realPath||ge[this.type](this);a=this.matrix?xe(a,this.matrix):a;for(var s=1;n+1>s;s++)i.push(r.path(a).attr({stroke:e.color,fill:e.fill?e.color:"none","stroke-linejoin":"round","stroke-linecap":"round","stroke-width":+(e.width/n*s).toFixed(3),opacity:+(e.opacity/n).toFixed(3)}));return i.insertBefore(this).translate(e.offsetx,e.offsety)};var tn=function(t,n,r,i,a,s,o,h,c){return null==c?u(t,n,r,i,a,s,o,h):e.findDotsAtSegment(t,n,r,i,a,s,o,h,l(t,n,r,i,a,s,o,h,c))},en=function(t,n){return function(r,i,a){r=Re(r);for(var s,o,u,l,h,c="",f={},p=0,d=0,g=r.length;g>d;d++){if(u=r[d],"M"==u[0])s=+u[1],o=+u[2];else{if(l=tn(s,o,u[1],u[2],u[3],u[4],u[5],u[6]),p+l>i){if(n&&!f.start){if(h=tn(s,o,u[1],u[2],u[3],u[4],u[5],u[6],i-p),c+=["C"+h.start.x,h.start.y,h.m.x,h.m.y,h.x,h.y],a)return c;f.start=c,c=["M"+h.x,h.y+"C"+h.n.x,h.n.y,h.end.x,h.end.y,u[5],u[6]].join(),p+=l,s=+u[5],o=+u[6];continue}if(!t&&!n)return h=tn(s,o,u[1],u[2],u[3],u[4],u[5],u[6],i-p),{x:h.x,y:h.y,alpha:h.alpha}}p+=l,s=+u[5],o=+u[6]}c+=u.shift()+u}return f.end=c,h=t?p:n?f:e.findDotsAtSegment(s,o,u[0],u[1],u[2],u[3],u[4],u[5],1),h.alpha&&(h={x:h.x,y:h.y,alpha:h.alpha}),h}},nn=en(1),rn=en(),an=en(0,1);e.getTotalLength=nn,e.getPointAtLength=rn,e.getSubpath=function(t,e,n){if(1e-6>this.getTotalLength(t)-n)return an(t,e).end;var r=an(t,n,1);return e?an(r,e).end:r},Ze.getTotalLength=function(){return"path"==this.type?this.node.getTotalLength?this.node.getTotalLength():nn(this.attrs.path):void 0},Ze.getPointAtLength=function(t){return"path"==this.type?rn(this.attrs.path,t):void 0},Ze.getSubpath=function(t,n){return"path"==this.type?e.getSubpath(this.attrs.path,t,n):void 0};var sn=e.easing_formulas={linear:function(t){return t},"<":function(t){return X(t,1.7)},">":function(t){return X(t,.48)},"<>":function(t){var e=.48-t/1.04,n=D.sqrt(.1734+e*e),r=n-e,i=X(V(r),1/3)*(0>r?-1:1),a=-n-e,s=X(V(a),1/3)*(0>a?-1:1),o=i+s+.5;return 3*(1-o)*o*o+o*o*o},backIn:function(t){var e=1.70158;return t*t*((e+1)*t-e)},backOut:function(t){t-=1;var e=1.70158;return t*t*((e+1)*t+e)+1},elastic:function(t){return t==!!t?t:X(2,-10*t)*D.sin((t-.075)*2*Y/.3)+1},bounce:function(t){var e,n=7.5625,r=2.75;return 1/r>t?e=n*t*t:2/r>t?(t-=1.5/r,e=n*t*t+.75):2.5/r>t?(t-=2.25/r,e=n*t*t+.9375):(t-=2.625/r,e=n*t*t+.984375),e}};sn.easeIn=sn["ease-in"]=sn["<"],sn.easeOut=sn["ease-out"]=sn[">"],sn.easeInOut=sn["ease-in-out"]=sn["<>"],sn["back-in"]=sn.backIn,sn["back-out"]=sn.backOut;var on=[],un=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){setTimeout(t,16)},ln=function(){for(var n=+new Date,r=0;on.length>r;r++){var i=on[r];if(!i.el.removed&&!i.paused){var a,s,o=n-i.start,u=i.ms,l=i.easing,h=i.from,c=i.diff,f=i.to,p=(i.t,i.el),d={},g={};if(i.initstatus?(o=(i.initstatus*i.anim.top-i.prev)/(i.percent-i.prev)*u,i.status=i.initstatus,delete i.initstatus,i.stop&&on.splice(r--,1)):i.status=(i.prev+(i.percent-i.prev)*(o/u))/i.anim.top,!(0>o))if(u>o){var x=l(o/u);for(var m in h)if(h[B](m)){switch(ne[m]){case G:a=+h[m]+x*u*c[m];break;case"colour":a="rgb("+[hn(Q(h[m].r+x*u*c[m].r)),hn(Q(h[m].g+x*u*c[m].g)),hn(Q(h[m].b+x*u*c[m].b))].join(",")+")";break;case"path":a=[];for(var y=0,b=h[m].length;b>y;y++){a[y]=[h[m][y][0]];for(var _=1,w=h[m][y].length;w>_;_++)a[y][_]=+h[m][y][_]+x*u*c[m][y][_];a[y]=a[y].join(E)}a=a.join(E);break;case"transform":if(c[m].real)for(a=[],y=0,b=h[m].length;b>y;y++)for(a[y]=[h[m][y][0]],_=1,w=h[m][y].length;w>_;_++)a[y][_]=h[m][y][_]+x*u*c[m][y][_];else{var k=function(t){return+h[m][t]+x*u*c[m][t]};a=[["m",k(0),k(1),k(2),k(3),k(4),k(5)]]}break;case"csv":if("clip-rect"==m)for(a=[],y=4;y--;)a[y]=+h[m][y]+x*u*c[m][y];break;default:var S=[][L](h[m]);for(a=[],y=p.paper.customAttributes[m].length;y--;)a[y]=+S[y]+x*u*c[m][y]}d[m]=a}p.attr(d),function(e,n,r){setTimeout(function(){t("raphael.anim.frame."+e,n,r)})}(p.id,p,i.anim)}else{if(function(n,r,i){setTimeout(function(){t("raphael.anim.frame."+r.id,r,i),t("raphael.anim.finish."+r.id,r,i),e.is(n,"function")&&n.call(r)})}(i.callback,p,i.anim),p.attr(f),on.splice(r--,1),i.repeat>1&&!i.next){for(s in f)f[B](s)&&(g[s]=i.totalOrigin[s]);i.el.attr(g),v(i.anim,i.el,i.anim.percents[0],null,i.totalOrigin,i.repeat-1)}i.next&&!i.stop&&v(i.anim,i.el,i.next,null,i.totalOrigin,i.repeat)}}}e.svg&&p&&p.paper&&p.paper.safari(),on.length&&un(ln)},hn=function(t){return t>255?255:0>t?0:t};Ze.animateWith=function(t,n,r,i,a,s){var o=this;if(o.removed)return s&&s.call(o),o;var u=r instanceof x?r:e.animation(r,i,a,s);v(u,o,u.percents[0],null,o.attr());for(var l=0,h=on.length;h>l;l++)if(on[l].anim==n&&on[l].el==t){on[h-1].start=on[l].start;break}return o},Ze.onAnimation=function(e){return e?t.on("raphael.anim.frame."+this.id,e):t.unbind("raphael.anim.frame."+this.id),this},x.prototype.delay=function(t){var e=new x(this.anim,this.ms);return e.times=this.times,e.del=+t||0,e},x.prototype.repeat=function(t){var e=new x(this.anim,this.ms);return e.del=this.del,e.times=D.floor(z(t,0))||1,e},e.animation=function(t,n,r,i){if(t instanceof x)return t;(e.is(r,"function")||!r)&&(i=i||r||null,r=null),t=Object(t),n=+n||0;var a,s,o={};for(s in t)t[B](s)&&J(s)!=s&&J(s)+"%"!=s&&(a=!0,o[s]=t[s]);return a?(r&&(o.easing=r),i&&(o.callback=i),new x({100:o},n)):new x(t,n)},Ze.animate=function(t,n,r,i){var a=this;if(a.removed)return i&&i.call(a),a;var s=t instanceof x?t:e.animation(t,n,r,i);return v(s,a,s.percents[0],null,a.attr()),a},Ze.setTime=function(t,e){return t&&null!=e&&this.status(t,O(e,t.ms)/t.ms),this},Ze.status=function(t,e){var n,r,i=[],a=0;if(null!=e)return v(t,this,-1,O(e,1)),this;for(n=on.length;n>a;a++)if(r=on[a],r.el.id==this.id&&(!t||r.anim==t)){if(t)return r.status;i.push({anim:r.anim,status:r.status})}return t?0:i},Ze.pause=function(e){for(var n=0;on.length>n;n++)on[n].el.id!=this.id||e&&on[n].anim!=e||t("raphael.anim.pause."+this.id,this,on[n].anim)!==!1&&(on[n].paused=!0);return this},Ze.resume=function(e){for(var n=0;on.length>n;n++)if(on[n].el.id==this.id&&(!e||on[n].anim==e)){var r=on[n];t("raphael.anim.resume."+this.id,this,r.anim)!==!1&&(delete r.paused,this.status(r.anim,r.status))}return this},Ze.stop=function(e){for(var n=0;on.length>n;n++)on[n].el.id!=this.id||e&&on[n].anim!=e||t("raphael.anim.stop."+this.id,this,on[n].anim)!==!1&&on.splice(n--,1);return this},t.on("raphael.remove",m),t.on("raphael.clear",m),Ze.toString=function(){return"Raphaël’s object"};var cn=function(t){if(this.items=[],this.length=0,this.type="set",t)for(var e=0,n=t.length;n>e;e++)!t[e]||t[e].constructor!=Ze.constructor&&t[e].constructor!=cn||(this[this.items.length]=this.items[this.items.length]=t[e],this.length++)},fn=cn.prototype;fn.push=function(){for(var t,e,n=0,r=arguments.length;r>n;n++)t=arguments[n],!t||t.constructor!=Ze.constructor&&t.constructor!=cn||(e=this.items.length,this[e]=this.items[e]=t,this.length++);return this},fn.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},fn.forEach=function(t,e){for(var n=0,r=this.items.length;r>n;n++)if(t.call(e,this.items[n],n)===!1)return this;return this};for(var pn in Ze)Ze[B](pn)&&(fn[pn]=function(t){return function(){var e=arguments;return this.forEach(function(n){n[t][T](n,e)})}}(pn));return fn.attr=function(t,n){if(t&&e.is(t,W)&&e.is(t[0],"object"))for(var r=0,i=t.length;i>r;r++)this.items[r].attr(t[r]);else for(var a=0,s=this.items.length;s>a;a++)this.items[a].attr(t,n);return this},fn.clear=function(){for(;this.length;)this.pop()},fn.splice=function(t,e){t=0>t?z(this.length+t,0):t,e=z(0,O(this.length-t,e));var n,r=[],i=[],a=[];for(n=2;arguments.length>n;n++)a.push(arguments[n]);for(n=0;e>n;n++)i.push(this[t+n]);for(;this.length-t>n;n++)r.push(this[t+n]);var s=a.length;for(n=0;s+r.length>n;n++)this.items[t+n]=this[t+n]=s>n?a[n]:r[n-s];for(n=this.items.length=this.length-=e-s;this[n];)delete this[n++];return new cn(i)},fn.exclude=function(t){for(var e=0,n=this.length;n>e;e++)if(this[e]==t)return this.splice(e,1),!0},fn.animate=function(t,n,r,i){(e.is(r,"function")||!r)&&(i=r||null);var a,s,o=this.items.length,u=o,l=this;if(!o)return this;i&&(s=function(){!--o&&i.call(l)}),r=e.is(r,N)?r:s;var h=e.animation(t,n,r,s);for(a=this.items[--u].animate(h);u--;)this.items[u]&&!this.items[u].removed&&this.items[u].animateWith(a,h,h);return this},fn.insertAfter=function(t){for(var e=this.items.length;e--;)this.items[e].insertAfter(t);return this},fn.getBBox=function(){for(var t=[],e=[],n=[],r=[],i=this.items.length;i--;)if(!this.items[i].removed){var a=this.items[i].getBBox();t.push(a.x),e.push(a.y),n.push(a.x+a.width),r.push(a.y+a.height)}return t=O[T](0,t),e=O[T](0,e),n=z[T](0,n),r=z[T](0,r),{x:t,y:e,x2:n,y2:r,width:n-t,height:r-e}},fn.clone=function(t){t=this.paper.set();for(var e=0,n=this.items.length;n>e;e++)t.push(this.items[e].clone());return t},fn.toString=function(){return"Raphaël‘s set"},fn.glow=function(t){var e=this.paper.set();return this.forEach(function(n){var r=n.glow(t);null!=r&&r.forEach(function(t){e.push(t)})}),e},e.registerFont=function(t){if(!t.face)return t;this.fonts=this.fonts||{};var e={w:t.w,face:{},glyphs:{}},n=t.face["font-family"];for(var r in t.face)t.face[B](r)&&(e.face[r]=t.face[r]);if(this.fonts[n]?this.fonts[n].push(e):this.fonts[n]=[e],!t.svg){e.face["units-per-em"]=K(t.face["units-per-em"],10);for(var i in t.glyphs)if(t.glyphs[B](i)){var a=t.glyphs[i];if(e.glyphs[i]={w:a.w,k:{},d:a.d&&"M"+a.d.replace(/[mlcxtrv]/g,function(t){return{l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"}[t]||"M"})+"z"},a.k)for(var s in a.k)a[B](s)&&(e.glyphs[i].k[s]=a.k[s])}}return t},b.getFont=function(t,n,r,i){if(i=i||"normal",r=r||"normal",n=+n||{normal:400,bold:700,lighter:300,bolder:800}[n]||400,e.fonts){var a=e.fonts[t];if(!a){var s=RegExp("(^|\\s)"+t.replace(/[^\w\d\s+!~.:_-]/g,P)+"(\\s|$)","i");for(var o in e.fonts)if(e.fonts[B](o)&&s.test(o)){a=e.fonts[o];break}}var u;if(a)for(var l=0,h=a.length;h>l&&(u=a[l],u.face["font-weight"]!=n||u.face["font-style"]!=r&&u.face["font-style"]||u.face["font-stretch"]!=i);l++);return u}},b.print=function(t,n,r,i,a,s,o){s=s||"middle",o=z(O(o||0,1),-1);var u,l=M(r)[I](P),h=0,c=0,f=P;if(e.is(i,"string")&&(i=this.getFont(i)),i){u=(a||16)/i.face["units-per-em"];for(var p=i.face.bbox[I](_),d=+p[0],g=p[3]-p[1],x=0,v=+p[1]+("baseline"==s?g+ +i.face.descent:g/2),m=0,y=l.length;y>m;m++){if("\n"==l[m])h=0,w=0,c=0,x+=g;else{var b=c&&i.glyphs[l[m-1]]||{},w=i.glyphs[l[m]];h+=c?(b.w||i.w)+(b.k&&b.k[l[m]]||0)+i.w*o:0,c=1}w&&w.d&&(f+=e.transformPath(w.d,["t",h*u,x*u,"s",u,u,d,v,"t",(t-d)/u,(n-v)/u]))}}return this.path(f).attr({fill:"#000",stroke:"none"})},b.add=function(t){if(e.is(t,"array"))for(var n,r=this.set(),i=0,a=t.length;a>i;i++)n=t[i]||{},w[B](n.type)&&r.push(this[n.type]().attr(n));return r},e.format=function(t,n){var r=e.is(n,W)?[0][L](n):arguments;return t&&e.is(t,N)&&r.length-1&&(t=t.replace(k,function(t,e){return null==r[++e]?P:r[e]})),t||P},e.fullfill=function(){var t=/\{([^\}]+)\}/g,e=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,n=function(t,n,r){var i=r;return n.replace(e,function(t,e,n,r,a){e=e||r,i&&(e in i&&(i=i[e]),"function"==typeof i&&a&&(i=i()))}),i=(null==i||i==r?t:i)+""};return function(e,r){return(e+"").replace(t,function(t,e){return n(t,e,r)})}}(),e.ninja=function(){return C.was?S.win.Raphael=C.is:delete Raphael,e},e.st=fn,function(t,n,r){function i(){/in/.test(t.readyState)?setTimeout(i,9):e.eve("raphael.DOMload")}null==t.readyState&&t.addEventListener&&(t.addEventListener(n,r=function(){t.removeEventListener(n,r,!1),t.readyState="complete"},!1),t.readyState="loading"),i()}(document,"DOMContentLoaded"),C.was?S.win.Raphael=e:Raphael=e,t.on("raphael.DOMload",function(){y=!0}),e});(function(t,e){"function"==typeof define&&define.amd?require(["raphael"],e):t.Raphael&&e(t.Raphael)})(this,function(t){if(t.svg){var e="hasOwnProperty",r=String,n=parseFloat,i=parseInt,a=Math,s=a.max,o=a.abs,u=a.pow,h=/[, ]+/,l=t.eve,c="",f=" ",p="http://www.w3.org/1999/xlink",d={block:"M5,0 0,2.5 5,5z",classic:"M5,0 0,2.5 5,5 3.5,3 3.5,2z",diamond:"M2.5,0 5,2.5 2.5,5 0,2.5z",open:"M6,1 1,3.5 6,6",oval:"M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"},g={};t.toString=function(){return"Your browser supports SVG.\nYou are running Raphaël "+this.version};var x=function(n,i){if(i){"string"==typeof n&&(n=x(n));for(var a in i)i[e](a)&&("xlink:"==a.substring(0,6)?n.setAttributeNS(p,a.substring(6),r(i[a])):n.setAttribute(a,r(i[a])))}else n=t._g.doc.createElementNS("http://www.w3.org/2000/svg",n),n.style&&(n.style.webkitTapHighlightColor="rgba(0,0,0,0)");return n},v=function(e,i){var h="linear",l=e.id+i,f=.5,p=.5,d=e.node,g=e.paper,v=d.style,y=t._g.doc.getElementById(l);if(!y){if(i=r(i).replace(t._radial_gradient,function(t,e,r){if(h="radial",e&&r){f=n(e),p=n(r);var i=2*(p>.5)-1;u(f-.5,2)+u(p-.5,2)>.25&&(p=a.sqrt(.25-u(f-.5,2))*i+.5)&&.5!=p&&(p=p.toFixed(5)-1e-5*i)}return c}),i=i.split(/\s*\-\s*/),"linear"==h){var m=i.shift();if(m=-n(m),isNaN(m))return null;var b=[0,0,a.cos(t.rad(m)),a.sin(t.rad(m))],_=1/(s(o(b[2]),o(b[3]))||1);b[2]*=_,b[3]*=_,0>b[2]&&(b[0]=-b[2],b[2]=0),0>b[3]&&(b[1]=-b[3],b[3]=0)}var w=t._parseDots(i);if(!w)return null;if(l=l.replace(/[\(\)\s,\xb0#]/g,"_"),e.gradient&&l!=e.gradient.id&&(g.defs.removeChild(e.gradient),delete e.gradient),!e.gradient){y=x(h+"Gradient",{id:l}),e.gradient=y,x(y,"radial"==h?{fx:f,fy:p}:{x1:b[0],y1:b[1],x2:b[2],y2:b[3],gradientTransform:e.matrix.invert()}),g.defs.appendChild(y);for(var k=0,C=w.length;C>k;k++)y.appendChild(x("stop",{offset:w[k].offset?w[k].offset:k?"100%":"0%","stop-color":w[k].color||"#fff"}))}}return x(d,{fill:"url(#"+l+")",opacity:1,"fill-opacity":1}),v.fill=c,v.opacity=1,v.fillOpacity=1,1},y=function(t){var e=t.getBBox(1);x(t.pattern,{patternTransform:t.matrix.invert()+" translate("+e.x+","+e.y+")"})},m=function(n,i,a){if("path"==n.type){for(var s,o,u,h,l,f=r(i).toLowerCase().split("-"),p=n.paper,v=a?"end":"start",y=n.node,m=n.attrs,b=m["stroke-width"],_=f.length,w="classic",k=3,C=3,B=5;_--;)switch(f[_]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":w=f[_];break;case"wide":C=5;break;case"narrow":C=2;break;case"long":k=5;break;case"short":k=2}if("open"==w?(k+=2,C+=2,B+=2,u=1,h=a?4:1,l={fill:"none",stroke:m.stroke}):(h=u=k/2,l={fill:m.stroke,stroke:"none"}),n._.arrows?a?(n._.arrows.endPath&&g[n._.arrows.endPath]--,n._.arrows.endMarker&&g[n._.arrows.endMarker]--):(n._.arrows.startPath&&g[n._.arrows.startPath]--,n._.arrows.startMarker&&g[n._.arrows.startMarker]--):n._.arrows={},"none"!=w){var S="raphael-marker-"+w,A="raphael-marker-"+v+w+k+C+(m.stroke || "").replace(/\(|,|\)/g, "");t._g.doc.getElementById(S)?g[S]++:(p.defs.appendChild(x(x("path"),{"stroke-linecap":"round",d:d[w],id:S})),g[S]=1);var T,M=t._g.doc.getElementById(A);M?(g[A]++,T=M.getElementsByTagName("use")[0]):(M=x(x("marker"),{id:A,markerHeight:C,markerWidth:k,orient:"auto",refX:h,refY:C/2}),T=x(x("use"),{"xlink:href":"#"+S,transform:(a?"rotate(180 "+k/2+" "+C/2+") ":c)+"scale("+k/B+","+C/B+")","stroke-width":(1/((k/B+C/B)/2)).toFixed(4)}),M.appendChild(T),p.defs.appendChild(M),g[A]=1),x(T,l);var F=u*("diamond"!=w&&"oval"!=w);a?(s=n._.arrows.startdx*b||0,o=t.getTotalLength(m.path)-F*b):(s=F*b,o=t.getTotalLength(m.path)-(n._.arrows.enddx*b||0)),l={},l["marker-"+v]="url(#"+A+")",(o||s)&&(l.d=Raphael.getSubpath(m.path,s,o)),x(y,l),n._.arrows[v+"Path"]=S,n._.arrows[v+"Marker"]=A,n._.arrows[v+"dx"]=F,n._.arrows[v+"Type"]=w,n._.arrows[v+"String"]=i}else a?(s=n._.arrows.startdx*b||0,o=t.getTotalLength(m.path)-s):(s=0,o=t.getTotalLength(m.path)-(n._.arrows.enddx*b||0)),n._.arrows[v+"Path"]&&x(y,{d:Raphael.getSubpath(m.path,s,o)}),delete n._.arrows[v+"Path"],delete n._.arrows[v+"Marker"],delete n._.arrows[v+"dx"],delete n._.arrows[v+"Type"],delete n._.arrows[v+"String"];for(l in g)if(g[e](l)&&!g[l]){var L=t._g.doc.getElementById(l);L&&L.parentNode.removeChild(L)}}},b={"":[0],none:[0],"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},_=function(t,e,n){if(e=b[r(e).toLowerCase()]){for(var i=t.attrs["stroke-width"]||"1",a={round:i,square:i,butt:0}[t.attrs["stroke-linecap"]||n["stroke-linecap"]]||0,s=[],o=e.length;o--;)s[o]=e[o]*i+(o%2?1:-1)*a;x(t.node,{"stroke-dasharray":s.join(",")})}},w=function(n,a){var u=n.node,l=n.attrs,f=u.style.visibility;u.style.visibility="hidden";for(var d in a)if(a[e](d)){if(!t._availableAttrs[e](d))continue;var g=a[d];switch(l[d]=g,d){case"blur":n.blur(g);break;case"href":case"title":case"target":var b=u.parentNode;if("a"!=b.tagName.toLowerCase()){var w=x("a");b.insertBefore(w,u),w.appendChild(u),b=w}"target"==d?b.setAttributeNS(p,"show","blank"==g?"new":g):b.setAttributeNS(p,d,g);break;case"cursor":u.style.cursor=g;break;case"transform":n.transform(g);break;case"arrow-start":m(n,g);break;case"arrow-end":m(n,g,1);break;case"clip-rect":var k=r(g).split(h);if(4==k.length){n.clip&&n.clip.parentNode.parentNode.removeChild(n.clip.parentNode);var B=x("clipPath"),S=x("rect");B.id=t.createUUID(),x(S,{x:k[0],y:k[1],width:k[2],height:k[3]}),B.appendChild(S),n.paper.defs.appendChild(B),x(u,{"clip-path":"url(#"+B.id+")"}),n.clip=S}if(!g){var A=u.getAttribute("clip-path");if(A){var T=t._g.doc.getElementById(A.replace(/(^url\(#|\)$)/g,c));T&&T.parentNode.removeChild(T),x(u,{"clip-path":c}),delete n.clip}}break;case"path":"path"==n.type&&(x(u,{d:g?l.path=t._pathToAbsolute(g):"M0,0"}),n._.dirty=1,n._.arrows&&("startString"in n._.arrows&&m(n,n._.arrows.startString),"endString"in n._.arrows&&m(n,n._.arrows.endString,1)));break;case"width":if(u.setAttribute(d,g),n._.dirty=1,!l.fx)break;d="x",g=l.x;case"x":l.fx&&(g=-l.x-(l.width||0));case"rx":if("rx"==d&&"rect"==n.type)break;case"cx":u.setAttribute(d,g),n.pattern&&y(n),n._.dirty=1;break;case"height":if(u.setAttribute(d,g),n._.dirty=1,!l.fy)break;d="y",g=l.y;case"y":l.fy&&(g=-l.y-(l.height||0));case"ry":if("ry"==d&&"rect"==n.type)break;case"cy":u.setAttribute(d,g),n.pattern&&y(n),n._.dirty=1;break;case"r":"rect"==n.type?x(u,{rx:g,ry:g}):u.setAttribute(d,g),n._.dirty=1;break;case"src":"image"==n.type&&u.setAttributeNS(p,"href",g);break;case"stroke-width":(1!=n._.sx||1!=n._.sy)&&(g/=s(o(n._.sx),o(n._.sy))||1),n.paper._vbSize&&(g*=n.paper._vbSize),u.setAttribute(d,g),l["stroke-dasharray"]&&_(n,l["stroke-dasharray"],a),n._.arrows&&("startString"in n._.arrows&&m(n,n._.arrows.startString),"endString"in n._.arrows&&m(n,n._.arrows.endString,1));break;case"stroke-dasharray":_(n,g,a);break;case"fill":var M=r(g).match(t._ISURL);if(M){B=x("pattern");var F=x("image");B.id=t.createUUID(),x(B,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1}),x(F,{x:0,y:0,"xlink:href":M[1]}),B.appendChild(F),function(e){t._preload(M[1],function(){var t=this.offsetWidth,r=this.offsetHeight;x(e,{width:t,height:r}),x(F,{width:t,height:r}),n.paper.safari()})}(B),n.paper.defs.appendChild(B),x(u,{fill:"url(#"+B.id+")"}),n.pattern=B,n.pattern&&y(n);break}var L=t.getRGB(g);if(L.error){if(("circle"==n.type||"ellipse"==n.type||"r"!=r(g).charAt())&&v(n,g)){if("opacity"in l||"fill-opacity"in l){var N=t._g.doc.getElementById(u.getAttribute("fill").replace(/^url\(#|\)$/g,c));if(N){var P=N.getElementsByTagName("stop");x(P[P.length-1],{"stop-opacity":("opacity"in l?l.opacity:1)*("fill-opacity"in l?l["fill-opacity"]:1)})}}l.gradient=g,l.fill="none";break}}else delete a.gradient,delete l.gradient,!t.is(l.opacity,"undefined")&&t.is(a.opacity,"undefined")&&x(u,{opacity:l.opacity}),!t.is(l["fill-opacity"],"undefined")&&t.is(a["fill-opacity"],"undefined")&&x(u,{"fill-opacity":l["fill-opacity"]});L[e]("opacity")&&x(u,{"fill-opacity":L.opacity>1?L.opacity/100:L.opacity});case"stroke":L=t.getRGB(g),u.setAttribute(d,L.hex),"stroke"==d&&L[e]("opacity")&&x(u,{"stroke-opacity":L.opacity>1?L.opacity/100:L.opacity}),"stroke"==d&&n._.arrows&&("startString"in n._.arrows&&m(n,n._.arrows.startString),"endString"in n._.arrows&&m(n,n._.arrows.endString,1));break;case"gradient":("circle"==n.type||"ellipse"==n.type||"r"!=r(g).charAt())&&v(n,g);break;case"opacity":l.gradient&&!l[e]("stroke-opacity")&&x(u,{"stroke-opacity":g>1?g/100:g});case"fill-opacity":if(l.gradient){N=t._g.doc.getElementById(u.getAttribute("fill").replace(/^url\(#|\)$/g,c)),N&&(P=N.getElementsByTagName("stop"),x(P[P.length-1],{"stop-opacity":g}));break}default:"font-size"==d&&(g=i(g,10)+"px");var E=d.replace(/(\-.)/g,function(t){return t.substring(1).toUpperCase()});u.style[E]=g,n._.dirty=1,u.setAttribute(d,g)}}C(n,a),u.style.visibility=f},k=1.2,C=function(n,a){if("text"==n.type&&(a[e]("text")||a[e]("font")||a[e]("font-size")||a[e]("x")||a[e]("y"))){var s=n.attrs,o=n.node,u=o.firstChild?i(t._g.doc.defaultView.getComputedStyle(o.firstChild,c).getPropertyValue("font-size"),10):10;if(a[e]("text")){for(s.text=a.text;o.firstChild;)o.removeChild(o.firstChild);for(var h,l=r(a.text).split("\n"),f=[],p=0,d=l.length;d>p;p++)h=x("tspan"),p&&x(h,{dy:u*k,x:s.x}),h.appendChild(t._g.doc.createTextNode(l[p])),o.appendChild(h),f[p]=h}else for(f=o.getElementsByTagName("tspan"),p=0,d=f.length;d>p;p++)p?x(f[p],{dy:u*k,x:s.x}):x(f[0],{dy:0});x(o,{x:s.x,y:s.y}),n._.dirty=1;var g=n._getBBox(),v=s.y-(g.y+g.height/2);v&&t.is(v,"finite")&&x(f[0],{dy:v})}},B=function(e,r){this[0]=this.node=e,e.raphael=!0,this.id=t._oid++,e.raphaelid=this.id,this.matrix=t.matrix(),this.realPath=null,this.paper=r,this.attrs=this.attrs||{},this._={transform:[],sx:1,sy:1,deg:0,dx:0,dy:0,dirty:1},!r.bottom&&(r.bottom=this),this.prev=r.top,r.top&&(r.top.next=this),r.top=this,this.next=null},S=t.el;B.prototype=S,S.constructor=B,t._engine.path=function(t,e){var r=x("path");e.canvas&&e.canvas.appendChild(r);var n=new B(r,e);return n.type="path",w(n,{fill:"none",stroke:"#000",path:t}),n},S.rotate=function(t,e,i){if(this.removed)return this;if(t=r(t).split(h),t.length-1&&(e=n(t[1]),i=n(t[2])),t=n(t[0]),null==i&&(e=i),null==e||null==i){var a=this.getBBox(1);e=a.x+a.width/2,i=a.y+a.height/2}return this.transform(this._.transform.concat([["r",t,e,i]])),this},S.scale=function(t,e,i,a){if(this.removed)return this;if(t=r(t).split(h),t.length-1&&(e=n(t[1]),i=n(t[2]),a=n(t[3])),t=n(t[0]),null==e&&(e=t),null==a&&(i=a),null==i||null==a)var s=this.getBBox(1);return i=null==i?s.x+s.width/2:i,a=null==a?s.y+s.height/2:a,this.transform(this._.transform.concat([["s",t,e,i,a]])),this},S.translate=function(t,e){return this.removed?this:(t=r(t).split(h),t.length-1&&(e=n(t[1])),t=n(t[0])||0,e=+e||0,this.transform(this._.transform.concat([["t",t,e]])),this)},S.transform=function(r){var n=this._;if(null==r)return n.transform;if(t._extractTransform(this,r),this.clip&&x(this.clip,{transform:this.matrix.invert()}),this.pattern&&y(this),this.node&&x(this.node,{transform:this.matrix}),1!=n.sx||1!=n.sy){var i=this.attrs[e]("stroke-width")?this.attrs["stroke-width"]:1;this.attr({"stroke-width":i})}return this},S.hide=function(){return!this.removed&&this.paper.safari(this.node.style.display="none"),this},S.show=function(){return!this.removed&&this.paper.safari(this.node.style.display=""),this},S.remove=function(){if(!this.removed&&this.node.parentNode){var e=this.paper;e.__set__&&e.__set__.exclude(this),l.unbind("raphael.*.*."+this.id),this.gradient&&e.defs.removeChild(this.gradient),t._tear(this,e),"a"==this.node.parentNode.tagName.toLowerCase()?this.node.parentNode.parentNode.removeChild(this.node.parentNode):this.node.parentNode.removeChild(this.node);for(var r in this)this[r]="function"==typeof this[r]?t._removedFactory(r):null;this.removed=!0}},S._getBBox=function(){if("none"==this.node.style.display){this.show();var t=!0}var e={};try{e=this.node.getBBox()}catch(r){}finally{e=e||{}}return t&&this.hide(),e},S.attr=function(r,n){if(this.removed)return this;if(null==r){var i={};for(var a in this.attrs)this.attrs[e](a)&&(i[a]=this.attrs[a]);return i.gradient&&"none"==i.fill&&(i.fill=i.gradient)&&delete i.gradient,i.transform=this._.transform,i}if(null==n&&t.is(r,"string")){if("fill"==r&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;if("transform"==r)return this._.transform;for(var s=r.split(h),o={},u=0,c=s.length;c>u;u++)r=s[u],o[r]=r in this.attrs?this.attrs[r]:t.is(this.paper.customAttributes[r],"function")?this.paper.customAttributes[r].def:t._availableAttrs[r];return c-1?o:o[s[0]]}if(null==n&&t.is(r,"array")){for(o={},u=0,c=r.length;c>u;u++)o[r[u]]=this.attr(r[u]);return o}if(null!=n){var f={};f[r]=n}else null!=r&&t.is(r,"object")&&(f=r);for(var p in f)l("raphael.attr."+p+"."+this.id,this,f[p]);for(p in this.paper.customAttributes)if(this.paper.customAttributes[e](p)&&f[e](p)&&t.is(this.paper.customAttributes[p],"function")){var d=this.paper.customAttributes[p].apply(this,[].concat(f[p]));this.attrs[p]=f[p];for(var g in d)d[e](g)&&(f[g]=d[g])}return w(this,f),this},S.toFront=function(){if(this.removed)return this;"a"==this.node.parentNode.tagName.toLowerCase()?this.node.parentNode.parentNode.appendChild(this.node.parentNode):this.node.parentNode.appendChild(this.node);var e=this.paper;return e.top!=this&&t._tofront(this,e),this},S.toBack=function(){if(this.removed)return this;var e=this.node.parentNode;return"a"==e.tagName.toLowerCase()?e.parentNode.insertBefore(this.node.parentNode,this.node.parentNode.parentNode.firstChild):e.firstChild!=this.node&&e.insertBefore(this.node,this.node.parentNode.firstChild),t._toback(this,this.paper),this.paper,this},S.insertAfter=function(e){if(this.removed)return this;var r=e.node||e[e.length-1].node;return r.nextSibling?r.parentNode.insertBefore(this.node,r.nextSibling):r.parentNode.appendChild(this.node),t._insertafter(this,e,this.paper),this},S.insertBefore=function(e){if(this.removed)return this;var r=e.node||e[0].node;return r.parentNode.insertBefore(this.node,r),t._insertbefore(this,e,this.paper),this},S.blur=function(e){var r=this;if(0!==+e){var n=x("filter"),i=x("feGaussianBlur");r.attrs.blur=e,n.id=t.createUUID(),x(i,{stdDeviation:+e||1.5}),n.appendChild(i),r.paper.defs.appendChild(n),r._blur=n,x(r.node,{filter:"url(#"+n.id+")"})}else r._blur&&(r._blur.parentNode.removeChild(r._blur),delete r._blur,delete r.attrs.blur),r.node.removeAttribute("filter")},t._engine.circle=function(t,e,r,n){var i=x("circle");t.canvas&&t.canvas.appendChild(i);var a=new B(i,t);return a.attrs={cx:e,cy:r,r:n,fill:"none",stroke:"#000"},a.type="circle",x(i,a.attrs),a},t._engine.rect=function(t,e,r,n,i,a){var s=x("rect");t.canvas&&t.canvas.appendChild(s);var o=new B(s,t);return o.attrs={x:e,y:r,width:n,height:i,r:a||0,rx:a||0,ry:a||0,fill:"none",stroke:"#000"},o.type="rect",x(s,o.attrs),o},t._engine.ellipse=function(t,e,r,n,i){var a=x("ellipse");t.canvas&&t.canvas.appendChild(a);var s=new B(a,t);return s.attrs={cx:e,cy:r,rx:n,ry:i,fill:"none",stroke:"#000"},s.type="ellipse",x(a,s.attrs),s},t._engine.image=function(t,e,r,n,i,a){var s=x("image");x(s,{x:r,y:n,width:i,height:a,preserveAspectRatio:"none"}),s.setAttributeNS(p,"href",e),t.canvas&&t.canvas.appendChild(s);var o=new B(s,t);return o.attrs={x:r,y:n,width:i,height:a,src:e},o.type="image",o},t._engine.text=function(e,r,n,i){var a=x("text");e.canvas&&e.canvas.appendChild(a);var s=new B(a,e);return s.attrs={x:r,y:n,"text-anchor":"middle",text:i,font:t._availableAttrs.font,stroke:"none",fill:"#000"},s.type="text",w(s,s.attrs),s},t._engine.setSize=function(t,e){return this.width=t||this.width,this.height=e||this.height,this.canvas.setAttribute("width",this.width),this.canvas.setAttribute("height",this.height),this._viewBox&&this.setViewBox.apply(this,this._viewBox),this},t._engine.create=function(){var e=t._getContainer.apply(0,arguments),r=e&&e.container,n=e.x,i=e.y,a=e.width,s=e.height;if(!r)throw Error("SVG container not found.");var o,u=x("svg"),h="overflow:hidden;";return n=n||0,i=i||0,a=a||512,s=s||342,x(u,{height:s,version:1.1,width:a,xmlns:"http://www.w3.org/2000/svg"}),1==r?(u.style.cssText=h+"position:absolute;left:"+n+"px;top:"+i+"px",t._g.doc.body.appendChild(u),o=1):(u.style.cssText=h+"position:relative",r.firstChild?r.insertBefore(u,r.firstChild):r.appendChild(u)),r=new t._Paper,r.width=a,r.height=s,r.canvas=u,r.clear(),r._left=r._top=0,o&&(r.renderfix=function(){}),r.renderfix(),r},t._engine.setViewBox=function(t,e,r,n,i){l("raphael.setViewBox",this,this._viewBox,[t,e,r,n,i]);var a,o,u=s(r/this.width,n/this.height),h=this.top,c=i?"meet":"xMinYMin";for(null==t?(this._vbSize&&(u=1),delete this._vbSize,a="0 0 "+this.width+f+this.height):(this._vbSize=u,a=t+f+e+f+r+f+n),x(this.canvas,{viewBox:a,preserveAspectRatio:c});u&&h;)o="stroke-width"in h.attrs?h.attrs["stroke-width"]:1,h.attr({"stroke-width":o}),h._.dirty=1,h._.dirtyT=1,h=h.prev;return this._viewBox=[t,e,r,n,!!i],this},t.prototype.renderfix=function(){var t,e=this.canvas,r=e.style;try{t=e.getScreenCTM()||e.createSVGMatrix()}catch(n){t=e.createSVGMatrix()}var i=-t.e%1,a=-t.f%1;(i||a)&&(i&&(this._left=(this._left+i)%1,r.left=this._left+"px"),a&&(this._top=(this._top+a)%1,r.top=this._top+"px"))},t.prototype.clear=function(){t.eve("raphael.clear",this);for(var e=this.canvas;e.firstChild;)e.removeChild(e.firstChild);this.bottom=this.top=null,(this.desc=x("desc")).appendChild(t._g.doc.createTextNode("Created with Raphaël "+t.version)),e.appendChild(this.desc),e.appendChild(this.defs=x("defs"))},t.prototype.remove=function(){l("raphael.remove",this),this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var e in this)this[e]="function"==typeof this[e]?t._removedFactory(e):null};var A=t.st;for(var T in S)S[e](T)&&!A[e](T)&&(A[T]=function(t){return function(){var e=arguments;return this.forEach(function(r){r[t].apply(r,e)})}}(T))}});(function(t,e){"function"==typeof define&&define.amd?require(["raphael"],e):t.Raphael&&e(t.Raphael)})(this,function(t){if(t.vml){var e="hasOwnProperty",r=String,i=parseFloat,n=Math,a=n.round,s=n.max,o=n.min,l=n.abs,h="fill",u=/[, ]+/,c=t.eve,f=" progid:DXImageTransform.Microsoft",p=" ",d="",g={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},x=/([clmz]),?([^clmz]*)/gi,v=/ progid:\S+Blur\([^\)]+\)/g,y=/-?[^,\s-]+/g,m="position:absolute;left:0;top:0;width:1px;height:1px",b=21600,_={path:1,rect:1,image:1},w={circle:1,ellipse:1},k=function(e){var i=/[ahqstv]/gi,n=t._pathToAbsolute;if(r(e).match(i)&&(n=t._path2curve),i=/[clmz]/g,n==t._pathToAbsolute&&!r(e).match(i)){var s=r(e).replace(x,function(t,e,r){var i=[],n="m"==e.toLowerCase(),s=g[e];return r.replace(y,function(t){n&&2==i.length&&(s+=i+g["m"==e?"l":"L"],i=[]),i.push(a(t*b))}),s+i});return s}var o,l,h=n(e);s=[];for(var u=0,c=h.length;c>u;u++){o=h[u],l=h[u][0].toLowerCase(),"z"==l&&(l="x");for(var f=1,v=o.length;v>f;f++)l+=a(o[f]*b)+(f!=v-1?",":d);s.push(l)}return s.join(p)},C=function(e,r,i){var n=t.matrix();return n.rotate(-e,.5,.5),{dx:n.x(r,i),dy:n.y(r,i)}},B=function(t,e,r,i,n,a){var s=t._,o=t.matrix,u=s.fillpos,c=t.node,f=c.style,d=1,g="",x=b/e,v=b/r;if(f.visibility="hidden",e&&r){if(c.coordsize=l(x)+p+l(v),f.rotation=a*(0>e*r?-1:1),a){var y=C(a,i,n);i=y.dx,n=y.dy}if(0>e&&(g+="x"),0>r&&(g+=" y")&&(d=-1),f.flip=g,c.coordorigin=i*-x+p+n*-v,u||s.fillsize){var m=c.getElementsByTagName(h);m=m&&m[0],c.removeChild(m),u&&(y=C(a,o.x(u[0],u[1]),o.y(u[0],u[1])),m.position=y.dx*d+p+y.dy*d),s.fillsize&&(m.size=s.fillsize[0]*l(e)+p+s.fillsize[1]*l(r)),c.appendChild(m)}f.visibility="visible"}};t.toString=function(){return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël "+this.version};var S=function(t,e,i){for(var n=r(e).toLowerCase().split("-"),a=i?"end":"start",s=n.length,o="classic",l="medium",h="medium";s--;)switch(n[s]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":o=n[s];break;case"wide":case"narrow":h=n[s];break;case"long":case"short":l=n[s]}var u=t.node.getElementsByTagName("stroke")[0];u[a+"arrow"]=o,u[a+"arrowlength"]=l,u[a+"arrowwidth"]=h},A=function(n,l){n.attrs=n.attrs||{};var c=n.node,f=n.attrs,g=c.style,x=_[n.type]&&(l.x!=f.x||l.y!=f.y||l.width!=f.width||l.height!=f.height||l.cx!=f.cx||l.cy!=f.cy||l.rx!=f.rx||l.ry!=f.ry||l.r!=f.r),v=w[n.type]&&(f.cx!=l.cx||f.cy!=l.cy||f.r!=l.r||f.rx!=l.rx||f.ry!=l.ry),y=n;for(var m in l)l[e](m)&&(f[m]=l[m]);if(x&&(f.path=t._getPath[n.type](n),n._.dirty=1),l.href&&(c.href=l.href),l.title&&(c.title=l.title),l.target&&(c.target=l.target),l.cursor&&(g.cursor=l.cursor),"blur"in l&&n.blur(l.blur),(l.path&&"path"==n.type||x)&&(c.path=k(~r(f.path).toLowerCase().indexOf("r")?t._pathToAbsolute(f.path):f.path),"image"==n.type&&(n._.fillpos=[f.x,f.y],n._.fillsize=[f.width,f.height],B(n,1,1,0,0,0))),"transform"in l&&n.transform(l.transform),v){var C=+f.cx,A=+f.cy,N=+f.rx||+f.r||0,E=+f.ry||+f.r||0;c.path=t.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x",a((C-N)*b),a((A-E)*b),a((C+N)*b),a((A+E)*b),a(C*b))}if("clip-rect"in l){var M=r(l["clip-rect"]).split(u);if(4==M.length){M[2]=+M[2]+ +M[0],M[3]=+M[3]+ +M[1];var z=c.clipRect||t._g.doc.createElement("div"),F=z.style;F.clip=t.format("rect({1}px {2}px {3}px {0}px)",M),c.clipRect||(F.position="absolute",F.top=0,F.left=0,F.width=n.paper.width+"px",F.height=n.paper.height+"px",c.parentNode.insertBefore(z,c),z.appendChild(c),c.clipRect=z)}l["clip-rect"]||c.clipRect&&(c.clipRect.style.clip="auto")}if(n.textpath){var R=n.textpath.style;l.font&&(R.font=l.font),l["font-family"]&&(R.fontFamily='"'+l["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g,d)+'"'),l["font-size"]&&(R.fontSize=l["font-size"]),l["font-weight"]&&(R.fontWeight=l["font-weight"]),l["font-style"]&&(R.fontStyle=l["font-style"])}if("arrow-start"in l&&S(y,l["arrow-start"]),"arrow-end"in l&&S(y,l["arrow-end"],1),null!=l.opacity||null!=l["stroke-width"]||null!=l.fill||null!=l.src||null!=l.stroke||null!=l["stroke-width"]||null!=l["stroke-opacity"]||null!=l["fill-opacity"]||null!=l["stroke-dasharray"]||null!=l["stroke-miterlimit"]||null!=l["stroke-linejoin"]||null!=l["stroke-linecap"]){var P=c.getElementsByTagName(h),I=!1;if(P=P&&P[0],!P&&(I=P=L(h)),"image"==n.type&&l.src&&(P.src=l.src),l.fill&&(P.on=!0),(null==P.on||"none"==l.fill||null===l.fill)&&(P.on=!1),P.on&&l.fill){var j=r(l.fill).match(t._ISURL);if(j){P.parentNode==c&&c.removeChild(P),P.rotate=!0,P.src=j[1],P.type="tile";var q=n.getBBox(1);P.position=q.x+p+q.y,n._.fillpos=[q.x,q.y],t._preload(j[1],function(){n._.fillsize=[this.offsetWidth,this.offsetHeight]})}else P.color=t.getRGB(l.fill).hex,P.src=d,P.type="solid",t.getRGB(l.fill).error&&(y.type in{circle:1,ellipse:1}||"r"!=r(l.fill).charAt())&&T(y,l.fill,P)&&(f.fill="none",f.gradient=l.fill,P.rotate=!1)}if("fill-opacity"in l||"opacity"in l){var D=((+f["fill-opacity"]+1||2)-1)*((+f.opacity+1||2)-1)*((+t.getRGB(l.fill).o+1||2)-1);D=o(s(D,0),1),P.opacity=D,P.src&&(P.color="none")}c.appendChild(P);var O=c.getElementsByTagName("stroke")&&c.getElementsByTagName("stroke")[0],V=!1;!O&&(V=O=L("stroke")),(l.stroke&&"none"!=l.stroke||l["stroke-width"]||null!=l["stroke-opacity"]||l["stroke-dasharray"]||l["stroke-miterlimit"]||l["stroke-linejoin"]||l["stroke-linecap"])&&(O.on=!0),("none"==l.stroke||null===l.stroke||null==O.on||0==l.stroke||0==l["stroke-width"])&&(O.on=!1);var Y=t.getRGB(l.stroke);O.on&&l.stroke&&(O.color=Y.hex),D=((+f["stroke-opacity"]+1||2)-1)*((+f.opacity+1||2)-1)*((+Y.o+1||2)-1);var G=.75*(i(l["stroke-width"])||1);if(D=o(s(D,0),1),null==l["stroke-width"]&&(G=f["stroke-width"]),l["stroke-width"]&&(O.weight=G),G&&1>G&&(D*=G)&&(O.weight=1),O.opacity=D,l["stroke-linejoin"]&&(O.joinstyle=l["stroke-linejoin"]||"miter"),O.miterlimit=l["stroke-miterlimit"]||8,l["stroke-linecap"]&&(O.endcap="butt"==l["stroke-linecap"]?"flat":"square"==l["stroke-linecap"]?"square":"round"),l["stroke-dasharray"]){var W={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};O.dashstyle=W[e](l["stroke-dasharray"])?W[l["stroke-dasharray"]]:d}V&&c.appendChild(O)}if("text"==y.type){y.paper.canvas.style.display=d;var X=y.paper.span,H=100,U=f.font&&f.font.match(/\d+(?:\.\d*)?(?=px)/);g=X.style,f.font&&(g.font=f.font),f["font-family"]&&(g.fontFamily=f["font-family"]),f["font-weight"]&&(g.fontWeight=f["font-weight"]),f["font-style"]&&(g.fontStyle=f["font-style"]),U=i(f["font-size"]||U&&U[0])||10,g.fontSize=U*H+"px",y.textpath.string&&(X.innerHTML=r(y.textpath.string).replace(/"));var $=X.getBoundingClientRect();y.W=f.w=($.right-$.left)/H,y.H=f.h=($.bottom-$.top)/H,y.X=f.x,y.Y=f.y+y.H/2,("x"in l||"y"in l)&&(y.path.v=t.format("m{0},{1}l{2},{1}",a(f.x*b),a(f.y*b),a(f.x*b)+1));for(var Z=["x","y","text","font","font-family","font-weight","font-style","font-size"],Q=0,J=Z.length;J>Q;Q++)if(Z[Q]in l){y._.dirty=1;break}switch(f["text-anchor"]){case"start":y.textpath.style["v-text-align"]="left",y.bbx=y.W/2;break;case"end":y.textpath.style["v-text-align"]="right",y.bbx=-y.W/2;break;default:y.textpath.style["v-text-align"]="center",y.bbx=0}y.textpath.style["v-text-kern"]=!0}},T=function(e,a,s){e.attrs=e.attrs||{};var o=(e.attrs,Math.pow),l="linear",h=".5 .5";if(e.attrs.gradient=a,a=r(a).replace(t._radial_gradient,function(t,e,r){return l="radial",e&&r&&(e=i(e),r=i(r),o(e-.5,2)+o(r-.5,2)>.25&&(r=n.sqrt(.25-o(e-.5,2))*(2*(r>.5)-1)+.5),h=e+p+r),d}),a=a.split(/\s*\-\s*/),"linear"==l){var u=a.shift();if(u=-i(u),isNaN(u))return null}var c=t._parseDots(a);if(!c)return null;if(e=e.shape||e.node,c.length){e.removeChild(s),s.on=!0,s.method="none",s.color=c[0].color,s.color2=c[c.length-1].color;for(var f=[],g=0,x=c.length;x>g;g++)c[g].offset&&f.push(c[g].offset+p+c[g].color);s.colors=f.length?f.join():"0% "+s.color,"radial"==l?(s.type="gradientTitle",s.focus="100%",s.focussize="0 0",s.focusposition=h,s.angle=0):(s.type="gradient",s.angle=(270-u)%360),e.appendChild(s)}return 1},N=function(e,r){this[0]=this.node=e,e.raphael=!0,this.id=t._oid++,e.raphaelid=this.id,this.X=0,this.Y=0,this.attrs={},this.paper=r,this.matrix=t.matrix(),this._={transform:[],sx:1,sy:1,dx:0,dy:0,deg:0,dirty:1,dirtyT:1},!r.bottom&&(r.bottom=this),this.prev=r.top,r.top&&(r.top.next=this),r.top=this,this.next=null},E=t.el;N.prototype=E,E.constructor=N,E.transform=function(e){if(null==e)return this._.transform;var i,n=this.paper._viewBoxShift,a=n?"s"+[n.scale,n.scale]+"-1-1t"+[n.dx,n.dy]:d;n&&(i=e=r(e).replace(/\.{3}|\u2026/g,this._.transform||d)),t._extractTransform(this,a+e);var s,o=this.matrix.clone(),l=this.skew,h=this.node,u=~r(this.attrs.fill).indexOf("-"),c=!r(this.attrs.fill).indexOf("url(");if(o.translate(-.5,-.5),c||u||"image"==this.type)if(l.matrix="1 0 0 1",l.offset="0 0",s=o.split(),u&&s.noRotation||!s.isSimple){h.style.filter=o.toFilter();var f=this.getBBox(),g=this.getBBox(1),x=f.x-g.x,v=f.y-g.y;h.coordorigin=x*-b+p+v*-b,B(this,1,1,x,v,0)}else h.style.filter=d,B(this,s.scalex,s.scaley,s.dx,s.dy,s.rotate);else h.style.filter=d,l.matrix=r(o),l.offset=o.offset();return i&&(this._.transform=i),this},E.rotate=function(t,e,n){if(this.removed)return this;if(null!=t){if(t=r(t).split(u),t.length-1&&(e=i(t[1]),n=i(t[2])),t=i(t[0]),null==n&&(e=n),null==e||null==n){var a=this.getBBox(1);e=a.x+a.width/2,n=a.y+a.height/2}return this._.dirtyT=1,this.transform(this._.transform.concat([["r",t,e,n]])),this}},E.translate=function(t,e){return this.removed?this:(t=r(t).split(u),t.length-1&&(e=i(t[1])),t=i(t[0])||0,e=+e||0,this._.bbox&&(this._.bbox.x+=t,this._.bbox.y+=e),this.transform(this._.transform.concat([["t",t,e]])),this)},E.scale=function(t,e,n,a){if(this.removed)return this;if(t=r(t).split(u),t.length-1&&(e=i(t[1]),n=i(t[2]),a=i(t[3]),isNaN(n)&&(n=null),isNaN(a)&&(a=null)),t=i(t[0]),null==e&&(e=t),null==a&&(n=a),null==n||null==a)var s=this.getBBox(1);return n=null==n?s.x+s.width/2:n,a=null==a?s.y+s.height/2:a,this.transform(this._.transform.concat([["s",t,e,n,a]])),this._.dirtyT=1,this},E.hide=function(){return!this.removed&&(this.node.style.display="none"),this},E.show=function(){return!this.removed&&(this.node.style.display=d),this},E._getBBox=function(){return this.removed?{}:{x:this.X+(this.bbx||0)-this.W/2,y:this.Y-this.H,width:this.W,height:this.H}},E.remove=function(){if(!this.removed&&this.node.parentNode){this.paper.__set__&&this.paper.__set__.exclude(this),t.eve.unbind("raphael.*.*."+this.id),t._tear(this,this.paper),this.node.parentNode.removeChild(this.node),this.shape&&this.shape.parentNode.removeChild(this.shape);for(var e in this)this[e]="function"==typeof this[e]?t._removedFactory(e):null;this.removed=!0}},E.attr=function(r,i){if(this.removed)return this;if(null==r){var n={};for(var a in this.attrs)this.attrs[e](a)&&(n[a]=this.attrs[a]);return n.gradient&&"none"==n.fill&&(n.fill=n.gradient)&&delete n.gradient,n.transform=this._.transform,n}if(null==i&&t.is(r,"string")){if(r==h&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;for(var s=r.split(u),o={},l=0,f=s.length;f>l;l++)r=s[l],o[r]=r in this.attrs?this.attrs[r]:t.is(this.paper.customAttributes[r],"function")?this.paper.customAttributes[r].def:t._availableAttrs[r];return f-1?o:o[s[0]]}if(this.attrs&&null==i&&t.is(r,"array")){for(o={},l=0,f=r.length;f>l;l++)o[r[l]]=this.attr(r[l]);return o}var p;null!=i&&(p={},p[r]=i),null==i&&t.is(r,"object")&&(p=r);for(var d in p)c("raphael.attr."+d+"."+this.id,this,p[d]);if(p){for(d in this.paper.customAttributes)if(this.paper.customAttributes[e](d)&&p[e](d)&&t.is(this.paper.customAttributes[d],"function")){var g=this.paper.customAttributes[d].apply(this,[].concat(p[d]));this.attrs[d]=p[d];for(var x in g)g[e](x)&&(p[x]=g[x])}p.text&&"text"==this.type&&(this.textpath.string=p.text),A(this,p)}return this},E.toFront=function(){return!this.removed&&this.node.parentNode.appendChild(this.node),this.paper&&this.paper.top!=this&&t._tofront(this,this.paper),this},E.toBack=function(){return this.removed?this:(this.node.parentNode.firstChild!=this.node&&(this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild),t._toback(this,this.paper)),this)},E.insertAfter=function(e){return this.removed?this:(e.constructor==t.st.constructor&&(e=e[e.length-1]),e.node.nextSibling?e.node.parentNode.insertBefore(this.node,e.node.nextSibling):e.node.parentNode.appendChild(this.node),t._insertafter(this,e,this.paper),this)},E.insertBefore=function(e){return this.removed?this:(e.constructor==t.st.constructor&&(e=e[0]),e.node.parentNode.insertBefore(this.node,e.node),t._insertbefore(this,e,this.paper),this)},E.blur=function(e){var r=this.node.runtimeStyle,i=r.filter;i=i.replace(v,d),0!==+e?(this.attrs.blur=e,r.filter=i+p+f+".Blur(pixelradius="+(+e||1.5)+")",r.margin=t.format("-{0}px 0 0 -{0}px",a(+e||1.5))):(r.filter=i,r.margin=0,delete this.attrs.blur)},t._engine.path=function(t,e){var r=L("shape");r.style.cssText=m,r.coordsize=b+p+b,r.coordorigin=e.coordorigin;var i=new N(r,e),n={fill:"none",stroke:"#000"};t&&(n.path=t),i.type="path",i.path=[],i.Path=d,A(i,n),e.canvas.appendChild(r);var a=L("skew");return a.on=!0,r.appendChild(a),i.skew=a,i.transform(d),i},t._engine.rect=function(e,r,i,n,a,s){var o=t._rectPath(r,i,n,a,s),l=e.path(o),h=l.attrs;return l.X=h.x=r,l.Y=h.y=i,l.W=h.width=n,l.H=h.height=a,h.r=s,h.path=o,l.type="rect",l},t._engine.ellipse=function(t,e,r,i,n){var a=t.path();return a.attrs,a.X=e-i,a.Y=r-n,a.W=2*i,a.H=2*n,a.type="ellipse",A(a,{cx:e,cy:r,rx:i,ry:n}),a},t._engine.circle=function(t,e,r,i){var n=t.path();return n.attrs,n.X=e-i,n.Y=r-i,n.W=n.H=2*i,n.type="circle",A(n,{cx:e,cy:r,r:i}),n},t._engine.image=function(e,r,i,n,a,s){var o=t._rectPath(i,n,a,s),l=e.path(o).attr({stroke:"none"}),u=l.attrs,c=l.node,f=c.getElementsByTagName(h)[0];return u.src=r,l.X=u.x=i,l.Y=u.y=n,l.W=u.width=a,l.H=u.height=s,u.path=o,l.type="image",f.parentNode==c&&c.removeChild(f),f.rotate=!0,f.src=r,f.type="tile",l._.fillpos=[i,n],l._.fillsize=[a,s],c.appendChild(f),B(l,1,1,0,0,0),l},t._engine.text=function(e,i,n,s){var o=L("shape"),l=L("path"),h=L("textpath");i=i||0,n=n||0,s=s||"",l.v=t.format("m{0},{1}l{2},{1}",a(i*b),a(n*b),a(i*b)+1),l.textpathok=!0,h.string=r(s),h.on=!0,o.style.cssText=m,o.coordsize=b+p+b,o.coordorigin="0 0";var u=new N(o,e),c={fill:"#000",stroke:"none",font:t._availableAttrs.font,text:s};u.shape=o,u.path=l,u.textpath=h,u.type="text",u.attrs.text=r(s),u.attrs.x=i,u.attrs.y=n,u.attrs.w=1,u.attrs.h=1,A(u,c),o.appendChild(h),o.appendChild(l),e.canvas.appendChild(o);var f=L("skew");return f.on=!0,o.appendChild(f),u.skew=f,u.transform(d),u},t._engine.setSize=function(e,r){var i=this.canvas.style;return this.width=e,this.height=r,e==+e&&(e+="px"),r==+r&&(r+="px"),i.width=e,i.height=r,i.clip="rect(0 "+e+" "+r+" 0)",this._viewBox&&t._engine.setViewBox.apply(this,this._viewBox),this},t._engine.setViewBox=function(e,r,i,n,a){t.eve("raphael.setViewBox",this,this._viewBox,[e,r,i,n,a]);var o,l,h=this.width,u=this.height,c=1/s(i/h,n/u);return a&&(o=u/n,l=h/i,h>i*o&&(e-=(h-i*o)/2/o),u>n*l&&(r-=(u-n*l)/2/l)),this._viewBox=[e,r,i,n,!!a],this._viewBoxShift={dx:-e,dy:-r,scale:c},this.forEach(function(t){t.transform("...")}),this};var L;t._engine.initWin=function(t){var e=t.document;e.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)");try{!e.namespaces.rvml&&e.namespaces.add("rvml","urn:schemas-microsoft-com:vml"),L=function(t){return e.createElement("')}}catch(r){L=function(t){return e.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},t._engine.initWin(t._g.win),t._engine.create=function(){var e=t._getContainer.apply(0,arguments),r=e.container,i=e.height,n=e.width,a=e.x,s=e.y;if(!r)throw Error("VML container not found.");var o=new t._Paper,l=o.canvas=t._g.doc.createElement("div"),h=l.style;return a=a||0,s=s||0,n=n||512,i=i||342,o.width=n,o.height=i,n==+n&&(n+="px"),i==+i&&(i+="px"),o.coordsize=1e3*b+p+1e3*b,o.coordorigin="0 0",o.span=t._g.doc.createElement("span"),o.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",l.appendChild(o.span),h.cssText=t.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",n,i),1==r?(t._g.doc.body.appendChild(l),h.left=a+"px",h.top=s+"px",h.position="absolute"):r.firstChild?r.insertBefore(l,r.firstChild):r.appendChild(l),o.renderfix=function(){},o},t.prototype.clear=function(){t.eve("raphael.clear",this),this.canvas.innerHTML=d,this.span=t._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},t.prototype.remove=function(){t.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var e in this)this[e]="function"==typeof this[e]?t._removedFactory(e):null;return!0};var M=t.st;for(var z in E)E[e](z)&&!M[e](z)&&(M[z]=function(t){return function(){var e=arguments;return this.forEach(function(r){r[t].apply(r,e)})}}(z))}}); \ No newline at end of file diff --git a/tools/pythonpkg/duckdb_query_graph/treant.js b/tools/pythonpkg/duckdb_query_graph/treant.js new file mode 100644 index 000000000000..fe300baa1edc --- /dev/null +++ b/tools/pythonpkg/duckdb_query_graph/treant.js @@ -0,0 +1,1330 @@ +/* +* Treant.js +* +* (c) 2013 Fran Peručić +* +* Treant is an open-source JavaScipt library for visualization of tree diagrams. +* It implements the node positioning algorithm of John Q. Walker II "Positioning nodes for General Trees". +* +* References: +* Emilio Cortegoso Lobato: ECOTree.js v1.0 (October 26th, 2006) +* +*/ + +;(function(){ + + var UTIL = { + inheritAttrs: function(me, from) { + for (var attr in from) { + if(typeof from[attr] !== 'function') { + if(me[attr] instanceof Object && from[attr] instanceof Object) { + this.inheritAttrs(me[attr], from[attr]); + } else { + me[attr] = from[attr]; + } + } + } + }, + + createMerge: function(obj1, obj2) { + var newObj = {}; + if(obj1) this.inheritAttrs(newObj, this.cloneObj(obj1)); + if(obj2) this.inheritAttrs(newObj, obj2); + return newObj; + }, + + cloneObj: function (obj) { + if (Object(obj) !== obj) { + return obj; + } + var res = new obj.constructor(); + for (var key in obj) if (obj["hasOwnProperty"](key)) { + res[key] = this.cloneObj(obj[key]); + } + return res; + }, + addEvent: function(el, eventType, handler) { + if (el.addEventListener) { // DOM Level 2 browsers + el.addEventListener(eventType, handler, false); + } else if (el.attachEvent) { // IE <= 8 + el.attachEvent('on' + eventType, handler); + } else { // ancient browsers + el['on' + eventType] = handler; + } + }, + + hasClass: function(element, my_class) { + return (" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf(" "+my_class+" ") > -1; + } + }; + + /** + * ImageLoader constructor. + * @constructor + * ImageLoader is used for determening if all the images from the Tree are loaded. + * Node size (width, height) can be correcty determined only when all inner images are loaded + */ + var ImageLoader = function() { + this.loading = []; + }; + + + ImageLoader.prototype = { + processNode: function(node) { + var images = node.nodeDOM.getElementsByTagName('img'), + i = images.length; + while(i--) { + this.create(node, images[i]); + } + }, + + removeAll: function(img_src) { + var i = this.loading.length; + while (i--) { + if (this.loading[i] === img_src) { this.loading.splice(i,1); } + } + }, + + create: function (node, image) { + + var self = this, + source = image.src; + this.loading.push(source); + + function imgTrigger() { + self.removeAll(source); + node.width = node.nodeDOM.offsetWidth; + node.height = node.nodeDOM.offsetHeight; + } + + if (image.complete) { return imgTrigger(); } + + UTIL.addEvent(image, 'load', imgTrigger); + UTIL.addEvent(image, 'error', imgTrigger); // handle broken url-s + + // load event is not fired for cached images, force the load event + image.src += "?" + new Date().getTime(); + }, + isNotLoading: function() { + return this.loading.length === 0; + } + }; + + /** + * Class: TreeStore + * @singleton + * TreeStore is used for holding initialized Tree objects + * Its purpose is to avoid global variables and enable multiple Trees on the page. + */ + + var TreeStore = { + store: [], + createTree: function(jsonConfig) { + this.store.push(new Tree(jsonConfig, this.store.length)); + return this.store[this.store.length - 1]; // return newly created tree + }, + get: function (treeId) { + return this.store[ treeId ]; + } + }; + + /** + * Tree constructor. + * @constructor + */ + var Tree = function (jsonConfig, treeId) { + + this.id = treeId; + + this.imageLoader = new ImageLoader(); + this.CONFIG = UTIL.createMerge(Tree.prototype.CONFIG, jsonConfig['chart']); + this.drawArea = document.getElementById(this.CONFIG.container.substring(1)); + this.drawArea.className += " Treant"; + this.nodeDB = new NodeDB(jsonConfig['nodeStructure'], this); + + // key store for storing reference to node connectors, + // key = nodeId where the connector ends + this.connectionStore = {}; + }; + + Tree.prototype = { + + positionTree: function(callback) { + + var self = this; + + if (this.imageLoader.isNotLoading()) { + + var root = this.root(), + orient = this.CONFIG.rootOrientation; + + this.resetLevelData(); + + this.firstWalk(root, 0); + this.secondWalk( root, 0, 0, 0 ); + + this.positionNodes(); + + if (this.CONFIG['animateOnInit']) { + setTimeout(function() { root.toggleCollapse(); }, this.CONFIG['animateOnInitDelay']); + } + + if(!this.loaded) { + this.drawArea.className += " loaded"; // nodes are hidden until .loaded class is add + if (Object.prototype.toString.call(callback) === "[object Function]") { callback(self); } + this.loaded = true; + } + + } else { + setTimeout(function() { self.positionTree(callback); }, 10); + } + }, + + /* + * In a first post-order walk, every node of the tree is + * assigned a preliminary x-coordinate (held in field + * node->flPrelim). In addition, internal nodes are + * given modifiers, which will be used to move their + * children to the right (held in field + * node->flModifier). + */ + firstWalk: function(node, level) { + + node.prelim = null; node.modifier = null; + + this.setNeighbors(node, level); + this.calcLevelDim(node, level); + + var leftSibling = node.leftSibling(); + + if(node.childrenCount() === 0 || level == this.CONFIG.maxDepth) { + // set preliminary x-coordinate + if(leftSibling) { + node.prelim = leftSibling.prelim + leftSibling.size() + this.CONFIG.siblingSeparation; + } else { + node.prelim = 0; + } + + } else { + //node is not a leaf, firstWalk for each child + for(var i = 0, n = node.childrenCount(); i < n; i++) { + this.firstWalk(node.childAt(i), level + 1); + } + + var midPoint = node.childrenCenter() - node.size() / 2; + + if(leftSibling) { + node.prelim = leftSibling.prelim + leftSibling.size() + this.CONFIG.siblingSeparation; + node.modifier = node.prelim - midPoint; + this.apportion( node, level ); + } else { + node.prelim = midPoint; + } + + // handle stacked children positioning + if(node.stackParent) { // hadle the parent of stacked children + node.modifier += this.nodeDB.get( node.stackChildren[0] ).size()/2 + node.connStyle.stackIndent; + } else if ( node.stackParentId ) { // handle stacked children + node.prelim = 0; + } + } + }, + + /* + * Clean up the positioning of small sibling subtrees. + * Subtrees of a node are formed independently and + * placed as close together as possible. By requiring + * that the subtrees be rigid at the time they are put + * together, we avoid the undesirable effects that can + * accrue from positioning nodes rather than subtrees. + */ + apportion: function (node, level) { + var firstChild = node.firstChild(), + firstChildLeftNeighbor = firstChild.leftNeighbor(), + compareDepth = 1, + depthToStop = this.CONFIG.maxDepth - level; + + while( firstChild && firstChildLeftNeighbor && compareDepth <= depthToStop ) { + // calculate the position of the firstChild, according to the position of firstChildLeftNeighbor + + var modifierSumRight = 0, + modifierSumLeft = 0, + leftAncestor = firstChildLeftNeighbor, + rightAncestor = firstChild; + + for(var i = 0; i < compareDepth; i++) { + + leftAncestor = leftAncestor.parent(); + rightAncestor = rightAncestor.parent(); + modifierSumLeft += leftAncestor.modifier; + modifierSumRight += rightAncestor.modifier; + // all the stacked children are oriented towards right so use right variables + if(rightAncestor.stackParent !== undefined) modifierSumRight += rightAncestor.size()/2; + } + + // find the gap between two trees and apply it to subTrees + // and mathing smaller gaps to smaller subtrees + + var totalGap = (firstChildLeftNeighbor.prelim + modifierSumLeft + firstChildLeftNeighbor.size() + this.CONFIG.subTeeSeparation) - (firstChild.prelim + modifierSumRight ); + + if(totalGap > 0) { + + var subtreeAux = node, + numSubtrees = 0; + + // count all the subtrees in the LeftSibling + while(subtreeAux && subtreeAux.id != leftAncestor.id) { + subtreeAux = subtreeAux.leftSibling(); + numSubtrees++; + } + + if(subtreeAux) { + + var subtreeMoveAux = node, + singleGap = totalGap / numSubtrees; + + while(subtreeMoveAux.id != leftAncestor.id) { + subtreeMoveAux.prelim += totalGap; + subtreeMoveAux.modifier += totalGap; + totalGap -= singleGap; + subtreeMoveAux = subtreeMoveAux.leftSibling(); + } + } + } + + compareDepth++; + + if(firstChild.childrenCount() === 0){ + firstChild = node.leftMost(0, compareDepth); + } else { + firstChild = firstChild.firstChild(); + } + if(firstChild) { + firstChildLeftNeighbor = firstChild.leftNeighbor(); + } + } + }, + + /* + * During a second pre-order walk, each node is given a + * final x-coordinate by summing its preliminary + * x-coordinate and the modifiers of all the node's + * ancestors. The y-coordinate depends on the height of + * the tree. (The roles of x and y are reversed for + * RootOrientations of EAST or WEST.) + */ + secondWalk: function( node, level, X, Y) { + + if(level <= this.CONFIG.maxDepth) { + var xTmp = node.prelim + X, + yTmp = Y, align = this.CONFIG.nodeAlign, + orinet = this.CONFIG.rootOrientation, + levelHeight, nodesizeTmp; + + if (orinet == 'NORTH' || orinet == 'SOUTH') { + + levelHeight = this.levelMaxDim[level].height; + nodesizeTmp = node.height; + if (node.pseudo) node.height = levelHeight; // assign a new size to pseudo nodes + } + else if (orinet == 'WEST' || orinet == 'EAST') { + + levelHeight = this.levelMaxDim[level].width; + nodesizeTmp = node.width; + if (node.pseudo) node.width = levelHeight; // assign a new size to pseudo nodes + } + + node.X = xTmp; + + if (node.pseudo) { // pseudo nodes need to be properly aligned, otherwise position is not correct in some examples + if (orinet == 'NORTH' || orinet == 'WEST') { + node.Y = yTmp; // align "BOTTOM" + } + else if (orinet == 'SOUTH' || orinet == 'EAST') { + node.Y = (yTmp + (levelHeight - nodesizeTmp)); // align "TOP" + } + + } else { + node.Y = ( align == 'CENTER' ) ? (yTmp + (levelHeight - nodesizeTmp) / 2) : + ( align == 'TOP' ) ? (yTmp + (levelHeight - nodesizeTmp)) : + yTmp; + } + + + if(orinet == 'WEST' || orinet == 'EAST') { + var swapTmp = node.X; + node.X = node.Y; + node.Y = swapTmp; + } + + if (orinet == 'SOUTH') { + + node.Y = -node.Y - nodesizeTmp; + } + else if (orinet == 'EAST') { + + node.X = -node.X - nodesizeTmp; + } + + if(node.childrenCount() !== 0) { + + if(node.id === 0 && this.CONFIG.hideRootNode) { + // ako je root node Hiden onda nemoj njegovu dijecu pomaknut po Y osi za Level separation, neka ona budu na vrhu + this.secondWalk(node.firstChild(), level + 1, X + node.modifier, Y); + } else { + + this.secondWalk(node.firstChild(), level + 1, X + node.modifier, Y + levelHeight + this.CONFIG.levelSeparation); + } + } + + if(node.rightSibling()) { + + this.secondWalk(node.rightSibling(), level, X, Y); + } + } + }, + + // position all the nodes, center the tree in center of its container + // 0,0 coordinate is in the upper left corner + positionNodes: function() { + + var self = this, + treeSize = { + x: self.nodeDB.getMinMaxCoord('X', null, null), + y: self.nodeDB.getMinMaxCoord('Y', null, null) + }, + + treeWidth = treeSize.x.max - treeSize.x.min, + treeHeight = treeSize.y.max - treeSize.y.min, + + treeCenter = { + x: treeSize.x.max - treeWidth/2, + y: treeSize.y.max - treeHeight/2 + }, + + containerCenter = { + x: self.drawArea.clientWidth/2, + y: self.drawArea.clientHeight/2 + }, + + deltaX = containerCenter.x - treeCenter.x, + deltaY = containerCenter.y - treeCenter.y, + + // all nodes must have positive X or Y coordinates, handle this with offsets + negOffsetX = ((treeSize.x.min + deltaX) <= 0) ? Math.abs(treeSize.x.min) : 0, + negOffsetY = ((treeSize.y.min + deltaY) <= 0) ? Math.abs(treeSize.y.min) : 0, + i, len, node; + + this.handleOverflow(treeWidth, treeHeight); + + // position all the nodes + for(i =0, len = this.nodeDB.db.length; i < len; i++) { + + node = this.nodeDB.get(i); + + if(node.id === 0 && this.CONFIG.hideRootNode) continue; + + // if the tree is smaller than the draw area, then center the tree within drawing area + node.X += negOffsetX + ((treeWidth < this.drawArea.clientWidth) ? deltaX : this.CONFIG.padding); + node.Y += negOffsetY + ((treeHeight < this.drawArea.clientHeight) ? deltaY : this.CONFIG.padding); + + var collapsedParent = node.collapsedParent(), + hidePoint = null; + + if(collapsedParent) { + // position the node behind the connector point of the parent, so future animations can be visible + hidePoint = collapsedParent.connectorPoint( true ); + node.hide(hidePoint); + + } else if(node.positioned) { + // node is allready positioned, + node.show(); + } else { // inicijalno stvaranje nodeova, postavi lokaciju + node.nodeDOM.style.left = node.X + 'px'; + node.nodeDOM.style.top = node.Y + 'px'; + + node.positioned = true; + } + + if (node.id !== 0 && !(node.parent().id === 0 && this.CONFIG.hideRootNode)) { + this.setConnectionToParent(node, hidePoint); // skip the root node + } + else if (!this.CONFIG.hideRootNode && node.drawLineThrough) { + // drawlinethrough is performed for for the root node also + node.drawLineThroughMe(); + } + } + + }, + + // create Raphael instance, set scrollbars if necessary + handleOverflow: function(treeWidth, treeHeight) { + + var viewWidth = (treeWidth < this.drawArea.clientWidth) ? this.drawArea.clientWidth : treeWidth + this.CONFIG.padding*2, + viewHeight = (treeHeight < this.drawArea.clientHeight) ? this.drawArea.clientHeight : treeHeight + this.CONFIG.padding*2; + + if(this._R) { + this._R.setSize(viewWidth, viewHeight); + } else { + this._R = this._R || Raphael(this.drawArea, viewWidth, viewHeight); + } + + + if(this.CONFIG.scrollbar == 'native') { + + if(this.drawArea.clientWidth < treeWidth) { // is owerflow-x necessary + this.drawArea.style.overflowX = "auto"; + } + + if(this.drawArea.clientHeight < treeHeight) { // is owerflow-y necessary + this.drawArea.style.overflowY = "auto"; + } + + } else if (this.CONFIG.scrollbar == 'fancy') { + + var jq_drawArea = $(this.drawArea); + if (jq_drawArea.hasClass('ps-container')) { // znaci da je 'fancy' vec inicijaliziran, treba updateat + + jq_drawArea.find('.Treant').css({ + width: viewWidth, + height: viewHeight + }); + + jq_drawArea.perfectScrollbar('update'); + + } else { + + var mainContiner = jq_drawArea.wrapInner('
    '), + child = mainContiner.find('.Treant'); + + child.css({ + width: viewWidth, + height: viewHeight + }); + + mainContiner.perfectScrollbar(); + } + } // else this.CONFIG.scrollbar == 'None' + + }, + + setConnectionToParent: function(node, hidePoint) { + + var stacked = node.stackParentId, + connLine, + parent = stacked ? this.nodeDB.get(stacked) : node.parent(), + + pathString = hidePoint ? this.getPointPathString(hidePoint): + this.getPathString(parent, node, stacked); + + if (this.connectionStore[node.id]) { + // connector allready exists, update the connector geometry + connLine = this.connectionStore[node.id]; + this.animatePath(connLine, pathString); + + } else { + + connLine = this._R.path( pathString ); + this.connectionStore[node.id] = connLine; + + // don't show connector arrows por pseudo nodes + if(node.pseudo) { delete parent.connStyle.style['arrow-end']; } + if(parent.pseudo) { delete parent.connStyle.style['arrow-start']; } + + connLine.attr(parent.connStyle.style); + + if(node.drawLineThrough || node.pseudo) { node.drawLineThroughMe(hidePoint); } + } + }, + + // create the parh which is represanted as a point, used for hideing the connection + getPointPathString: function(hp) { + // "_" indicates the path will be hidden + return ["_M", hp.x, ",", hp.y, 'L', hp.x, ",", hp.y, hp.x, ",", hp.y].join(" "); + }, + + animatePath: function(path, pathString) { + + if (path.hidden && pathString.charAt(0) !== "_") { // path will be shown, so show it + path.show(); + path.hidden = false; + } + + path.animate({ + path: pathString.charAt(0) === "_" ? pathString.substring(1) : pathString // remove the "_" prefix if it exists + }, this.CONFIG['animation']['connectorsSpeed'], this.CONFIG['animation']['connectorsAnimation'], + function(){ + if(pathString.charAt(0) === "_") { // animation is hideing the path, hide it at the and of animation + path.hide(); + path.hidden = true; + } + + }); + + }, + + getPathString: function(from_node, to_node, stacked) { + + var startPoint = from_node.connectorPoint( true ), + endPoint = to_node.connectorPoint( false ), + orinet = this.CONFIG.rootOrientation, + connType = from_node.connStyle.type, + P1 = {}, P2 = {}; + + if (orinet == 'NORTH' || orinet == 'SOUTH') { + P1.y = P2.y = (startPoint.y + endPoint.y) / 2; + + P1.x = startPoint.x; + P2.x = endPoint.x; + + } else if (orinet == 'EAST' || orinet == 'WEST') { + P1.x = P2.x = (startPoint.x + endPoint.x) / 2; + + P1.y = startPoint.y; + P2.y = endPoint.y; + } + + // sp, p1, pm, p2, ep == "x,y" + var sp = startPoint.x+','+startPoint.y, p1 = P1.x+','+P1.y, p2 = P2.x+','+P2.y, ep = endPoint.x+','+endPoint.y, + pm = (P1.x + P2.x)/2 +','+ (P1.y + P2.y)/2, pathString, stackPoint; + + if(stacked) { // STACKED CHILDREN + + stackPoint = (orinet == 'EAST' || orinet == 'WEST') ? + endPoint.x+','+startPoint.y : + startPoint.x+','+endPoint.y; + + if( connType == "step" || connType == "straight" ) { + + pathString = ["M", sp, 'L', stackPoint, 'L', ep]; + + } else if ( connType == "curve" || connType == "bCurve" ) { + + var helpPoint, // used for nicer curve lines + indent = from_node.connStyle.stackIndent; + + if (orinet == 'NORTH') { + helpPoint = (endPoint.x - indent)+','+(endPoint.y - indent); + } else if (orinet == 'SOUTH') { + helpPoint = (endPoint.x - indent)+','+(endPoint.y + indent); + } else if (orinet == 'EAST') { + helpPoint = (endPoint.x + indent) +','+startPoint.y; + } else if ( orinet == 'WEST') { + helpPoint = (endPoint.x - indent) +','+startPoint.y; + } + + pathString = ["M", sp, 'L', helpPoint, 'S', stackPoint, ep]; + } + + } else { // NORAML CHILDREN + + if( connType == "step" ) { + pathString = ["M", sp, 'L', p1, 'L', p2, 'L', ep]; + } else if ( connType == "curve" ) { + pathString = ["M", sp, 'C', p1, p2, ep ]; + } else if ( connType == "bCurve" ) { + pathString = ["M", sp, 'Q', p1, pm, 'T', ep]; + } else if (connType == "straight" ) { + pathString = ["M", sp, 'L', sp, ep]; + } + } + + return pathString.join(" "); + }, + + // algorithm works from left to right, so previous processed node will be left neigbor of the next node + setNeighbors: function(node, level) { + + node.leftNeighborId = this.lastNodeOnLevel[level]; + if(node.leftNeighborId) node.leftNeighbor().rightNeighborId = node.id; + this.lastNodeOnLevel[level] = node.id; + }, + + // used for calculation of height and width of a level (level dimensions) + calcLevelDim: function(node, level) { // root node is on level 0 + if (this.levelMaxDim[level]) { + if( this.levelMaxDim[level].width < node.width ) + this.levelMaxDim[level].width = node.width; + + if( this.levelMaxDim[level].height < node.height ) + this.levelMaxDim[level].height = node.height; + + } else { + this.levelMaxDim[level] = { width: node.width, height: node.height }; + } + }, + + resetLevelData: function() { + this.lastNodeOnLevel = []; + this.levelMaxDim = []; + }, + + root: function() { + return this.nodeDB.get( 0 ); + } + }; + + /** + * NodeDB constructor. + * @constructor + * NodeDB is used for storing the nodes. Each tree has its own NodeDB. + */ + var NodeDB = function (nodeStructure, tree) { + + this.db = []; + + var self = this; + + function itterateChildren(node, parentId) { + + var newNode = self.createNode(node, parentId, tree, null); + + if(node['children']) { + + newNode.children = []; + + // pseudo node is used for descending children to the next level + if(node['childrenDropLevel'] && node['childrenDropLevel'] > 0) { + while(node['childrenDropLevel']--) { + // pseudo node needs to inherit the connection style from its parent for continuous connectors + var connStyle = UTIL.cloneObj(newNode.connStyle); + newNode = self.createNode('pseudo', newNode.id, tree, null); + newNode.connStyle = connStyle; + newNode.children = []; + } + } + + var stack = (node['stackChildren'] && !self.hasGrandChildren(node)) ? newNode.id : null; + + // svildren are position on separate leves, one beneeth the other + if (stack !== null) { newNode.stackChildren = []; } + + for (var i = 0, len = node['children'].length; i < len ; i++) { + + if (stack !== null) { + newNode = self.createNode(node['children'][i], newNode.id, tree, stack); + if((i + 1) < len) newNode.children = []; // last node cant have children + } else { + itterateChildren(node['children'][i], newNode.id); + } + } + } + } + + if (tree.CONFIG['animateOnInit']) nodeStructure['collapsed'] = true; + + itterateChildren( nodeStructure, -1); // root node + + this.createGeometries(tree); + }; + + NodeDB.prototype = { + + createGeometries: function(tree) { + var i = this.db.length, node; + while(i--) { + this.get(i).createGeometry(tree); + } + }, + + get: function (nodeId) { + return this.db[nodeId]; // get node by ID + }, + + createNode: function(nodeStructure, parentId, tree, stackParentId) { + + var node = new TreeNode( nodeStructure, this.db.length, parentId, tree, stackParentId ); + + this.db.push( node ); + if( parentId >= 0 ) this.get( parentId ).children.push( node.id ); //skip root node + + if( stackParentId ) { + this.get( stackParentId ).stackParent = true; + this.get( stackParentId ).stackChildren.push( node.id ); + } + + return node; + }, + + getMinMaxCoord: function( dim, parent, MinMax ) { // used for getting the dimensions of the tree, dim = 'X' || 'Y' + // looks for min and max (X and Y) within the set of nodes + var parent = parent || this.get(0), + i = parent.childrenCount(), + MinMax = MinMax || { // start with root node dimensions + min: parent[dim], + max: parent[dim] + ((dim == 'X') ? parent.width : parent.height) + }; + + while(i--) { + + var node = parent.childAt(i), + maxTest = node[dim] + ((dim == 'X') ? node.width : node.height), + minTest = node[dim]; + + if (maxTest > MinMax.max) { + MinMax.max = maxTest; + + } + if (minTest < MinMax.min) { + MinMax.min = minTest; + } + + this.getMinMaxCoord(dim, node, MinMax); + } + return MinMax; + }, + + hasGrandChildren: function(nodeStructure) { + var i = nodeStructure.children.length; + while(i--) { + if(nodeStructure.children[i].children) return true; + } + } + }; + + + /** + * TreeNode constructor. + * @constructor + */ + var TreeNode = function (nodeStructure, id, parentId, tree, stackParentId) { + + this.id = id; + this.parentId = parentId; + this.treeId = tree.id; + this.prelim = 0; + this.modifier = 0; + + this.stackParentId = stackParentId; + + // pseudo node is a node with width=height=0, it is invisible, but necessary for the correct positiong of the tree + this.pseudo = nodeStructure === 'pseudo' || nodeStructure['pseudo']; + + this.image = nodeStructure['image']; + + this.link = UTIL.createMerge( tree.CONFIG.node.link, nodeStructure['link']); + + this.connStyle = UTIL.createMerge(tree.CONFIG.connectors, nodeStructure['connectors']); + + this.drawLineThrough = nodeStructure['drawLineThrough'] === false ? false : nodeStructure['drawLineThrough'] || tree.CONFIG.node['drawLineThrough']; + + this.collapsable = nodeStructure['collapsable'] === false ? false : nodeStructure['collapsable'] || tree.CONFIG.node['collapsable']; + this.collapsed = nodeStructure['collapsed']; + + this.text = nodeStructure['text']; + + // '.node' DIV + this.nodeInnerHTML = nodeStructure['innerHTML']; + this.nodeHTMLclass = (tree.CONFIG.node['HTMLclass'] ? tree.CONFIG.node['HTMLclass'] : '') + // globaly defined class for the nodex + (nodeStructure['HTMLclass'] ? (' ' + nodeStructure['HTMLclass']) : ''); // + specific node class + + this.nodeHTMLid = nodeStructure['HTMLid']; + }; + + TreeNode.prototype = { + + Tree: function() { + return TreeStore.get(this.treeId); + }, + + dbGet: function(nodeId) { + return this.Tree().nodeDB.get(nodeId); + }, + + size: function() { // returns the width of the node + var orient = this.Tree().CONFIG.rootOrientation; + + if(this.pseudo) return - this.Tree().CONFIG.subTeeSeparation; // prevents of separateing the subtrees + + if (orient == 'NORTH' || orient == 'SOUTH') + return this.width; + + else if (orient == 'WEST' || orient == 'EAST') + return this.height; + }, + + childrenCount: function () { + return (this.collapsed || !this.children) ? 0 : this.children.length; + }, + + childAt: function(i) { + return this.dbGet( this.children[i] ); + }, + + firstChild: function() { + return this.childAt(0); + }, + + lastChild: function() { + return this.childAt( this.children.length - 1 ); + }, + + parent: function() { + return this.dbGet( this.parentId ); + }, + + leftNeighbor: function() { + if( this.leftNeighborId ) return this.dbGet( this.leftNeighborId ); + }, + + rightNeighbor: function() { + if( this.rightNeighborId ) return this.dbGet( this.rightNeighborId ); + }, + + leftSibling: function () { + var leftNeighbor = this.leftNeighbor(); + + if( leftNeighbor && leftNeighbor.parentId == this.parentId ) return leftNeighbor; + }, + + rightSibling: function () { + var rightNeighbor = this.rightNeighbor(); + + if( rightNeighbor && rightNeighbor.parentId == this.parentId ) return rightNeighbor; + }, + + childrenCenter: function ( tree ) { + var first = this.firstChild(), + last = this.lastChild(); + return first.prelim + ((last.prelim - first.prelim) + last.size()) / 2; + }, + + // find out if one of the node ancestors is collapsed + collapsedParent: function() { + var parent = this.parent(); + if (!parent) return false; + if (parent.collapsed) return parent; + return parent.collapsedParent(); + }, + + leftMost: function ( level, depth ) { // returns the leftmost child at specific level, (initaial level = 0) + + if( level >= depth ) return this; + if( this.childrenCount() === 0 ) return; + + for(var i = 0, n = this.childrenCount(); i < n; i++) { + + var leftmostDescendant = this.childAt(i).leftMost( level + 1, depth ); + if(leftmostDescendant) + return leftmostDescendant; + } + }, + + // returns start or the end point of the connector line, origin is upper-left + connectorPoint: function(startPoint) { + var orient = this.Tree().CONFIG.rootOrientation, point = {}; + + if(this.stackParentId) { // return different end point if node is a stacked child + if (orient == 'NORTH' || orient == 'SOUTH') { orient = 'WEST'; } + else if (orient == 'EAST' || orient == 'WEST') { orient = 'NORTH'; } + } + // if pseudo, a virtual center is used + if (orient == 'NORTH') { + + point.x = (this.pseudo) ? this.X - this.Tree().CONFIG.subTeeSeparation/2 : this.X + this.width/2; + point.y = (startPoint) ? this.Y + this.height : this.Y; + + } else if (orient == 'SOUTH') { + + point.x = (this.pseudo) ? this.X - this.Tree().CONFIG.subTeeSeparation/2 : this.X + this.width/2; + point.y = (startPoint) ? this.Y : this.Y + this.height; + + } else if (orient == 'EAST') { + + point.x = (startPoint) ? this.X : this.X + this.width; + point.y = (this.pseudo) ? this.Y - this.Tree().CONFIG.subTeeSeparation/2 : this.Y + this.height/2; + + } else if (orient == 'WEST') { + + point.x = (startPoint) ? this.X + this.width : this.X; + point.y = (this.pseudo) ? this.Y - this.Tree().CONFIG.subTeeSeparation/2 : this.Y + this.height/2; + } + return point; + }, + + pathStringThrough: function() { // get the geometry of a path going through the node + var startPoint = this.connectorPoint(true), + endPoint = this.connectorPoint(false); + + return ["M", startPoint.x+","+startPoint.y, 'L', endPoint.x+","+endPoint.y].join(" "); + }, + + drawLineThroughMe: function(hidePoint) { // hidepoint se proslijedjuje ako je node sakriven zbog collapsed + + var pathString = hidePoint ? this.Tree().getPointPathString(hidePoint) : this.pathStringThrough(); + + this.lineThroughMe = this.lineThroughMe || this.Tree()._R.path(pathString); + + var line_style = UTIL.cloneObj(this.connStyle.style); + + delete line_style['arrow-start']; + delete line_style['arrow-end']; + + this.lineThroughMe.attr( line_style ); + + if(hidePoint) { + this.lineThroughMe.hide(); + this.lineThroughMe.hidden = true; + } + }, + + addSwitchEvent: function(my_switch) { + var self = this; + UTIL.addEvent(my_switch, 'click', function(){ + self.toggleCollapse(); + }); + }, + + toggleCollapse: function() { + var tree = this.Tree(); + + if (! tree.inAnimation) { + + tree.inAnimation = true; + + this.collapsed = !this.collapsed; // toglle the collapse at each click + if (this.collapsed) { + $(this.nodeDOM).addClass('collapsed'); + } else { + $(this.nodeDOM).removeClass('collapsed'); + } + tree.positionTree(); + + setTimeout(function() { // set the flag after the animation + tree.inAnimation = false; + }, tree.CONFIG['animation']['nodeSpeed'] > tree.CONFIG['animation']['connectorsSpeed'] ? tree.CONFIG['animation']['nodeSpeed'] : tree.CONFIG['animation']['connectorsSpeed']) + } + }, + + hide: function(collapse_to_point) { + this.nodeDOM.style.overflow = "hidden"; + + var jq_node = $(this.nodeDOM), tree = this.Tree(), + config = tree.CONFIG, + new_pos = { + left: collapse_to_point.x, + top: collapse_to_point.y + }; + + if (!this.hidden) { new_pos.width = new_pos.height = 0; } + + // store old width + height - padding problem when returning back to old state + if(!this.startW || !this.startH) { this.startW = jq_node.width(); this.startH = jq_node.height(); } + + // if parent was hidden in initial configuration, position the node behind the parent without animations + if(!this.positioned || this.hidden) { + this.nodeDOM.style.visibility = 'hidden'; + jq_node.css(new_pos); + this.positioned = true; + } else { + jq_node.animate(new_pos, config['animation']['nodeSpeed'], config['animation']['nodeAnimation'], + function(){ + this.style.visibility = 'hidden'; + }); + } + + // animate the line through node if the line exists + if(this.lineThroughMe) { + var new_path = tree.getPointPathString(collapse_to_point); + if (this.hidden) { + // update without animations + this.lineThroughMe.attr({path: new_path}); + } else { + // update with animations + tree.animatePath(this.lineThroughMe, tree.getPointPathString(collapse_to_point)); + } + } + + this.hidden = true; + }, + + show: function() { + this.nodeDOM.style.visibility = 'visible'; + + var new_pos = { + left: this.X, + top: this.Y + }, + tree = this.Tree(), config = tree.CONFIG; + + // if the node was hidden, update width and height + if(this.hidden) { + new_pos['width'] = this.startW; + new_pos['height'] = this.startH; + } + + $(this.nodeDOM).animate( + new_pos, + config['animation']['nodeSpeed'], config['animation']['nodeAnimation'], + function() { + // $.animate applys "overflow:hidden" to the node, remove it to avoid visual problems + this.style.overflow = ""; + } + ); + + if(this.lineThroughMe) { + tree.animatePath(this.lineThroughMe, this.pathStringThrough()); + } + + this.hidden = false; + } + }; + + TreeNode.prototype.createGeometry = function(tree) { + + if (this.id === 0 && tree.CONFIG.hideRootNode) { + this.width = 0; this.height = 0; + return; + } + + var drawArea = tree.drawArea, + image, i, + + /////////// CREATE NODE ////////////// + node = this.link.href ? document.createElement('a') : document.createElement('div'); + + node.className = (!this.pseudo) ? TreeNode.prototype.CONFIG.nodeHTMLclass : 'pseudo'; + if(this.nodeHTMLclass && !this.pseudo) node.className += ' ' + this.nodeHTMLclass; + + if(this.nodeHTMLid) node.id = this.nodeHTMLid; + + if(this.link.href) { + node.href = this.link.href; + node.target = this.link.target; + } + + /////////// CREATE innerHTML ////////////// + if (!this.pseudo) { + if (!this.nodeInnerHTML) { + + // IMAGE + if(this.image) { + image = document.createElement('img'); + + image.src = this.image; + node.appendChild(image); + } + + // TEXT + if(this.text) { + for(var key in this.text) { + if(TreeNode.prototype.CONFIG.textClass[key]) { + var text = document.createElement(this.text[key].href ? 'a' : 'p'); + + // meke an element if required + if (this.text[key].href) { + text.href = this.text[key].href; + if (this.text[key].target) { text.target = this.text[key].target; } + } + //.split('<br>').join('
    ') + text.className = TreeNode.prototype.CONFIG.textClass[key]; + text.appendChild(document.createTextNode( + this.text[key].val ? this.text[key].val : + this.text[key] instanceof Object ? "'val' param missing!" : this.text[key] + )); + text.innerHTML = text.innerHTML.split('<br>').join('
    ') + + node.appendChild(text); + } + } + } + + } else { + + // get some element by ID and clone its structure into a node + if (this.nodeInnerHTML.charAt(0) === "#") { + var elem = document.getElementById(this.nodeInnerHTML.substring(1)); + if (elem) { + node = elem.cloneNode(true); + node.id += "-clone"; + node.className += " node"; + } else { + node.innerHTML = " Wrong ID selector "; + } + } else { + // insert your custom HTML into a node + node.innerHTML = this.nodeInnerHTML; + } + } + + // handle collapse switch + if (this.collapsed || (this.collapsable && this.childrenCount() && !this.stackParentId)) { + var my_switch = document.createElement('a'); + my_switch.className = "collapse-switch"; + node.appendChild(my_switch); + this.addSwitchEvent(my_switch); + if (this.collapsed) { node.className += " collapsed"; } + } + } + + /////////// APPEND all ////////////// + drawArea.appendChild(node); + + this.width = node.offsetWidth; + this.height = node.offsetHeight; + + this.nodeDOM = node; + + tree.imageLoader.processNode(this); + }; + + + + // ########################################### + // Expose global + default CONFIG params + // ########################################### + + Tree.prototype.CONFIG = { + 'maxDepth': 100, + 'rootOrientation': 'NORTH', // NORTH || EAST || WEST || SOUTH + 'nodeAlign': 'CENTER', // CENTER || TOP || BOTTOM + 'levelSeparation': 30, + 'siblingSeparation': 30, + 'subTeeSeparation': 30, + + 'hideRootNode': false, + + 'animateOnInit': false, + 'animateOnInitDelay': 500, + + 'padding': 15, // the difference is seen only when the scrollbar is shown + 'scrollbar': "native", // "native" || "fancy" || "None" (PS: "fancy" requires jquery and perfect-scrollbar) + + 'connectors': { + + 'type': 'curve', // 'curve' || 'step' || 'straight' || 'bCurve' + 'style': { + 'stroke': 'black' + }, + 'stackIndent': 15 + }, + + 'node': { // each node inherits this, it can all be overrifen in node config + + // HTMLclass: 'node', + // drawLineThrough: false, + // collapsable: false, + 'link': { + 'target': "_self" + } + }, + + 'animation': { // each node inherits this, it can all be overrifen in node config + + 'nodeSpeed': 450, + 'nodeAnimation': "linear", + 'connectorsSpeed': 450, + 'connectorsAnimation': "linear" + } + }; + + TreeNode.prototype.CONFIG = { + 'nodeHTMLclass': 'node', + + 'textClass': { + 'name': 'node-name', + 'title': 'node-title', + 'desc': 'node-desc', + 'contact': 'node-contact' + } + }; + + // ############################################# + // Makes a JSON chart config out of Array config + // ############################################# + + var JSOnconfig = { + make: function( configArray ) { + + var i = configArray.length, node; + + this.jsonStructure = { + 'chart': null, + 'nodeStructure': null + }; + //fist loop: find config, find root; + while(i--) { + node = configArray[i]; + if (node.hasOwnProperty('container')) { + this.jsonStructure.chart = node; + continue; + } + + if (!node.hasOwnProperty('parent') && ! node.hasOwnProperty('container')) { + this.jsonStructure.nodeStructure = node; + node.myID = this.getID(); + } + } + + this.findChildren(configArray); + + return this.jsonStructure; + }, + + findChildren: function(nodes) { + var parents = [0]; // start witha a root node + + while(parents.length) { + var parentId = parents.pop(), + parent = this.findNode(this.jsonStructure.nodeStructure, parentId), + i = 0, len = nodes.length, + children = []; + + for(;i Date: Tue, 6 Dec 2022 13:51:27 +0100 Subject: [PATCH 06/26] can create graphs from the package now. Still need to implement some extra 'nice' features --- scripts/generate_querygraph.py | 268 +----------------- .../pythonpkg/duckdb_query_graph/__init__.py | 227 ++++++++++++--- .../duckdb_query_graph/query_graph.css | 154 ++-------- 3 files changed, 214 insertions(+), 435 deletions(-) diff --git a/scripts/generate_querygraph.py b/scripts/generate_querygraph.py index 20697d21babc..c7dcfaa7ba11 100644 --- a/scripts/generate_querygraph.py +++ b/scripts/generate_querygraph.py @@ -3,58 +3,12 @@ # and converts it into a Query Graph. import os -from functools import reduce - -class NodeTiming: - - def __init__(self, phase, time): - self.phase = phase - self.time = time - # percentage is determined later. - self.percentage = 0 - - def calculate_percentage(self, total_time): - self.percentage = self.time/total_time - - def combine_timing(l, r): - # TODO: can only add timings for same-phase nodes - total_time = l.time + r.time - return NodeTiming(l.phase, total_time) - -class AllTimings: - - def __init__(self): - self.phase_to_timings = {} - - def add_node_timing(self, node_timing): - if node_timing.phase in self.phase_to_timings: - self.phase_to_timings[node_timing.phase].append(node_timing) - return - self.phase_to_timings[node_timing.phase] = [node_timing] - - def get_phase_timings(self, phase): - return self.phase_to_timings[phase] - - def get_summary_phase_timings(self, phase): - return reduce(NodeTiming.combine_timing, self.phase_to_timings[phase]) - - def get_phases_high_to_low(self): - phases = list(self.phase_to_timings.keys()) - phases.sort(key=lambda x: (self.get_summary_phase_timings(x)).time) - phases.reverse() - return phases - - def get_sum_of_all_timings(self): - total_timing_sum = 0 - for phase in self.phase_to_timings.keys(): - total_timing_sum += self.get_summary_phase_timings(phase).time - return total_timing_sum - -QUERY_TIMINGS = AllTimings() +import sys +from python_helpers import open_utf8 sys.path.insert(0, 'benchmark') -import duckdb_query_graph +from tools.pythonpkg.duckdb_query_graph import generate arguments = sys.argv if len(arguments) <= 1: @@ -81,224 +35,10 @@ def get_sum_of_all_timings(self): print("Incorrect input for open_output, expected TRUE or FALSE") exit(1) -def open_utf8(fpath, flags): - import sys - if sys.version_info[0] < 3: - return open(fpath, flags) - else: - return open(fpath, flags, encoding="utf8") - -def get_node_body(name, result, cardinality, timing, extra_info): - body = "" - body += "
    " - body += f"

    {name}

    " - body += f"

    {result}s

    " - if timing: - body += f"

    {timing}

    " - body += f"

    cardinality = {cardinality}

    " - # if extra_info: - # body += f"

    {extra_info}

    " - body += "
    " - body += "
    " - return body - - -def generate_tree_recursive(json_graph): - node_prefix_html = "
  • " - node_suffix_html = "
  • " - node_body = get_node_body(json_graph["name"], - json_graph["timing"], - json_graph["cardinality"], - json_graph["extra_info"].replace("\n", "
    "), - json_graph["timings"]) - - children_html = "" - if len(json_graph['children']) >= 1: - children_html += "
      " - for child in json_graph["children"]: - children_html += generate_tree_recursive(child) - children_html += "
    " - return node_prefix_html + node_body + children_html + node_suffix_html - -# if detailed profiling is enabled, then information on how long the optimizer/physical planner etc. is available -# that is "top level timing information" -def get_top_level_timings(json): - return [] - -def get_child_timings(top_node): - global QUERY_TIMINGS - node_timing = NodeTiming(top_node['name'], float(top_node['timing'])) - QUERY_TIMINGS.add_node_timing(node_timing) - for child in top_node['children']: - get_child_timings(child) - -def gather_timing_information(json): - # add up all of the times - # measure each time as a percentage of the total time. - # then you can return a list of [phase, time, percentage] - top_level_timings = get_top_level_timings(json) - child_timings = get_child_timings(json['children'][0]) - -# For generating the table in the top left. -def generate_timing_html(graph_json): - global QUERY_TIMINGS - json_graph = json.loads(graph_json) - gather_timing_information(json_graph) - total_time = float(json_graph['timing']) - table_head = """ - - - - - - - - """ - - table_body = "" - table_end = "
    PhaseTimePercentage
    " -# total_time_row = f""" -# -# TOTAL TIME -# {total_time} -# -# -# """ - execution_time = QUERY_TIMINGS.get_sum_of_all_timings() -# total_node_time_row = f""" -# -# Execution -# {} -# -# -# """ - - all_phases = QUERY_TIMINGS.get_phases_high_to_low() - all_phases = [NodeTiming("TOTAL TIME", total_time), NodeTiming("Execution Time", execution_time)] + all_phases - for phase in all_phases: - summarized_phase = QUERY_TIMINGS.get_summary_phase_timings(phase) - summarized_phase.calculate_percentage(total_time) - table_body += f""" - - {phase} - {summarized_phase.time} - {str(summarized_phase.percentage * 100)[:6]}% - -""" - table_body += table_end - return table_head + table_body - - -def generate_tree_html(graph_json): - json_graph = json.loads(graph_json) - tree_prefix = "
    \n
      " - tree_suffix = "
    " - # first level of json is general overview - # FIXME: make sure json output first level always has only 1 level - tree_body = generate_tree_recursive(json_graph['children'][0]) - return tree_prefix + tree_body + tree_suffix - - -def generate_style_html(graph_json, include_meta_info): - css = "\n" - return { - 'css': css, - 'libraries': '', - 'chart_script': '' - } - - -def generate_ipython(json_input): - from IPython.core.display import HTML - - html_output = generate_html(json_input, False) - - return HTML(""" - ${CSS} - ${LIBRARIES} -
    - ${CHART_SCRIPT} - """.replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', html_output['chart_script']).replace('${LIBRARIES}', html_output['libraries'])) - - -def generate(input_file, output_file): - with open_utf8(input_file, 'r') as f: - text = f.read() - - html_output = generate_style_html(text, True) - timing_table = generate_timing_html(text) - tree_output = generate_tree_html(text) - - # finally create and write the html - with open_utf8(output_file, "w+") as f: - html = """ - - - - - Query Profile Graph for Query - ${CSS} - - - -
    -
    - ${TIMING_TABLE} -
    - - ${TREE} - - -""" - html = html.replace("${CSS}", html_output['css']) - html = html.replace("${TIMING_TABLE}", timing_table) - html = html.replace('[INFOSEPARATOR]', '
    ') - html = html.replace('${TREE}', tree_output) - f.write(html) - generate(input, output) with open(output, 'r') as f: text = f.read() if open_output: - os.system('open "' + output.replace('"', '\\"') + '"') + os.system('open "' + output.replace('"', '\\"') + '"') \ No newline at end of file diff --git a/tools/pythonpkg/duckdb_query_graph/__init__.py b/tools/pythonpkg/duckdb_query_graph/__init__.py index 23bc490f388d..8615aa59f80b 100644 --- a/tools/pythonpkg/duckdb_query_graph/__init__.py +++ b/tools/pythonpkg/duckdb_query_graph/__init__.py @@ -1,10 +1,55 @@ import json import os +from functools import reduce qgraph_css = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'query_graph.css') -raphael_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'raphael.js') -treant_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'treant.js') -profile_output_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'parse_profiling_output.js') + +class NodeTiming: + + def __init__(self, phase, time): + self.phase = phase + self.time = time + # percentage is determined later. + self.percentage = 0 + + def calculate_percentage(self, total_time): + self.percentage = self.time/total_time + + def combine_timing(l, r): + # TODO: can only add timings for same-phase nodes + total_time = l.time + r.time + return NodeTiming(l.phase, total_time) + +class AllTimings: + + def __init__(self): + self.phase_to_timings = {} + + def add_node_timing(self, node_timing): + if node_timing.phase in self.phase_to_timings: + self.phase_to_timings[node_timing.phase].append(node_timing) + return + self.phase_to_timings[node_timing.phase] = [node_timing] + + def get_phase_timings(self, phase): + return self.phase_to_timings[phase] + + def get_summary_phase_timings(self, phase): + return reduce(NodeTiming.combine_timing, self.phase_to_timings[phase]) + + def get_phases_high_to_low(self): + phases = list(self.phase_to_timings.keys()) + phases.sort(key=lambda x: (self.get_summary_phase_timings(x)).time) + phases.reverse() + return phases + + def get_sum_of_all_timings(self): + total_timing_sum = 0 + for phase in self.phase_to_timings.keys(): + total_timing_sum += self.get_summary_phase_timings(phase).time + return total_timing_sum + +QUERY_TIMINGS = AllTimings() def open_utf8(fpath, flags): import sys @@ -14,39 +59,98 @@ def open_utf8(fpath, flags): return open(fpath, flags, encoding="utf8") -def generate_html(graph_json, include_meta_info): - libraries = "" +# if detailed profiling is enabled, then information on how long the optimizer/physical planner etc. is available +# that is "top level timing information" +def get_top_level_timings(json): + return [] + +def get_child_timings(top_node): + global QUERY_TIMINGS + node_timing = NodeTiming(top_node['name'], float(top_node['timing'])) + QUERY_TIMINGS.add_node_timing(node_timing) + for child in top_node['children']: + get_child_timings(child) + +def get_node_body(name, result, cardinality, extra_info, timing): + body = "" + body += "
    " + body += f"

    {name} ({result}s)

    " + body += f"

    cardinality = {cardinality}

    " + if extra_info: + extra_info = extra_info.replace("[INFOSEPARATOR]", "----") + extra_info = extra_info.replace("

    ", "
    ----
    ") + body += f"

    {extra_info}

    " + # TODO: Expand on timing. Usually available from a detailed profiling + body += "
    " + body += "
    " + return body + + +def generate_tree_recursive(json_graph): + node_prefix_html = "
  • " + node_suffix_html = "
  • " + node_body = get_node_body(json_graph["name"], + json_graph["timing"], + json_graph["cardinality"], + json_graph["extra_info"].replace("\n", "
    "), + json_graph["timings"]) + + children_html = "" + if len(json_graph['children']) >= 1: + children_html += "
      " + for child in json_graph["children"]: + children_html += generate_tree_recursive(child) + children_html += "
    " + return node_prefix_html + node_body + children_html + node_suffix_html + +# For generating the table in the top left. +def generate_timing_html(graph_json): + global QUERY_TIMINGS + json_graph = json.loads(graph_json) + gather_timing_information(json_graph) + total_time = float(json_graph['timing']) + table_head = """ + + + + + + + + """ + + table_body = "" + table_end = "
    PhaseTimePercentage
    " + + execution_time = QUERY_TIMINGS.get_sum_of_all_timings() + + all_phases = QUERY_TIMINGS.get_phases_high_to_low() + QUERY_TIMINGS.add_node_timing(NodeTiming("TOTAL TIME", total_time)) + QUERY_TIMINGS.add_node_timing(NodeTiming("Execution Time", execution_time)) + all_phases = ["TOTAL TIME", "Execution Time"] + all_phases + for phase in all_phases: + summarized_phase = QUERY_TIMINGS.get_summary_phase_timings(phase) + summarized_phase.calculate_percentage(total_time) + phase_column = f"{phase}" if phase == "TOTAL TIME" or phase == "Execution Time" else phase + table_body += f""" + + {phase_column} + {summarized_phase.time} + {str(summarized_phase.percentage * 100)[:6]}% + +""" + table_body += table_end + return table_head + table_body + +def generate_tree_html(graph_json): + json_graph = json.loads(graph_json) + tree_prefix = "
    \n
      " + tree_suffix = "
    " + # first level of json is general overview + # FIXME: make sure json output first level always has only 1 level + tree_body = generate_tree_recursive(json_graph['children'][0]) + return tree_prefix + tree_body + tree_suffix - css = "" - - graph_json = graph_json.replace('\n', ' ').replace("'", "\\'").replace("\\n", "\\\\n") - - chart_script = """ - """.replace("${GRAPH_JSON}", graph_json).replace("${META_INFO}", "" if not include_meta_info else "document.getElementById('meta-info').innerHTML = meta_info;") - return { - 'css': css, - 'libraries': libraries, - 'chart_script': chart_script - } def generate_ipython(json_input): from IPython.core.display import HTML @@ -61,29 +165,62 @@ def generate_ipython(json_input): """.replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', html_output['chart_script']).replace('${LIBRARIES}', html_output['libraries'])) +def generate_style_html(graph_json, include_meta_info): + treeflex_css = "\n" + css = "\n" + return { + 'treeflex_css': treeflex_css, + 'duckdb_css': css, + 'libraries': '', + 'chart_script': '' + } + +def gather_timing_information(json): + # add up all of the times + # measure each time as a percentage of the total time. + # then you can return a list of [phase, time, percentage] + top_level_timings = get_top_level_timings(json) + child_timings = get_child_timings(json['children'][0]) + def generate(input_file, output_file): with open_utf8(input_file, 'r') as f: text = f.read() - html_output = generate_html(text, True) - print(html_output['chart_script']) + html_output = generate_style_html(text, True) + timing_table = generate_timing_html(text) + tree_output = generate_tree_html(text) + # finally create and write the html with open_utf8(output_file, "w+") as f: - f.write(""" + html = """ Query Profile Graph for Query - ${CSS} + ${TREEFLEX_CSS} + - ${LIBRARIES} -
    -
    - - ${CHART_SCRIPT} +
    + ${TIMING_TABLE} +
    + ${TREE} -""".replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', html_output['chart_script']).replace('${LIBRARIES}', html_output['libraries'])) +""" + html = html.replace("${TREEFLEX_CSS}", html_output['treeflex_css']) + html = html.replace("${DUCKDB_CSS}", html_output['duckdb_css']) + html = html.replace("${TIMING_TABLE}", timing_table) + html = html.replace('${TREE}', tree_output) + f.write(html) + + +output = "output_detailed.json" +generate("output_detailed.json", "output_detailed.html") diff --git a/tools/pythonpkg/duckdb_query_graph/query_graph.css b/tools/pythonpkg/duckdb_query_graph/query_graph.css index 99f4d83f63de..4faffdbcab0f 100644 --- a/tools/pythonpkg/duckdb_query_graph/query_graph.css +++ b/tools/pythonpkg/duckdb_query_graph/query_graph.css @@ -1,136 +1,38 @@ - -.Treant { position: relative; overflow: hidden; padding: 0 !important; } -.Treant > .node, -.Treant > .pseudo { position: absolute; display: block; visibility: hidden; } -.Treant.loaded .node, -.Treant.loaded .pseudo { visibility: visible; } -.Treant > .pseudo { width: 0; height: 0; border: none; padding: 0; } -.Treant .collapse-switch { width: 3px; height: 3px; display: block; border: 1px solid black; position: absolute; top: 1px; right: 1px; cursor: pointer; } -.Treant .collapsed .collapse-switch { background-color: #868DEE; } -.Treant > .node img { border: none; float: left; } - -body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td { margin:0; padding:0; } -table { border-collapse:collapse; border-spacing:0; } -fieldset,img { border:0; } -address,caption,cite,code,dfn,em,strong,th,var { font-style:normal; font-weight:normal; } -caption,th { text-align:left; } -h1,h2,h3,h4,h5,h6 { font-size:100%; font-weight:normal; } -q:before,q:after { content:''; } -abbr,acronym { border:0; } - -body { background: #fff; } -/* optional Container STYLES */ -.chart { height: 500px; margin: 5px; width: 100%; } -.Treant > .node { } -.Treant > p { font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-weight: bold; font-size: 12px; } - -.tree-node { - padding: 2px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - background-color: #ffffff; - border: 1px solid #000; - width: 200px; - font-family: Tahoma; - font-size: 12px; -} - -.metainfo { - font-family: Tahoma; - font-weight: bold; -} - -.metainfo > th { - padding: 1px 5px; -} - -.mainphase { - background-color: #668D3C; -} -.subphase { - background-color: #B99C6B; -} -.phaseheader { - background-color: #83929F; -} - -.node-name { - font-weight: bold; - text-align: center; - font-size: 14px; -} - -.node-title { - text-align: center; -} - -.node-contact { - font-weight: bold; - text-align: center; +.styled-table { + border-collapse: collapse; + margin: 25px 0; + font-size: 0.9em; + font-family: sans-serif; + min-width: 400px; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); } -th { - background:none; +.styled-table thead tr { + background-color: #009879; + color: #ffffff; + text-align: left; } -.small-impact { - opacity: 0.7; +.styled-table th, +.styled-table td { + padding: 12px 15px; } - -.medium-impact { - opacity: 0.8; +.styled-table tbody tr { + border-bottom: 1px solid #dddddd; } -.high-impact { - opacity: 0.9; +.styled-table tbody tr:nth-of-type(even) { + background-color: #f3f3f3; } -.most-impact { - opacity: 1; +.styled-table tbody tr:last-of-type { + border-bottom: 2px solid #009879; } -.tone-1 { - background-color: #493829; -} -.tone-2 { - background-color: #816C5B; -} -.tone-3 { - background-color: #A9A18C; -} -.tone-4 { - background-color: #613318; -} -.tone-5 { - background-color: #B99C6B; -} -.tone-6 { - background-color: #A15038; -} -.tone-7 { - background-color: #8F3B1B; -} -.tone-8 { - background-color: #D57500; +.node-body { + font-size:15px; } -.tone-9 { - background-color: #DBCA69; -} -.tone-10 { - background-color: #404F24; -} -.tone-11 { - background-color: #668D3C; -} -.tone-12 { - background-color: #BDD09F; -} -.tone-13 { - background-color: #4E6172; -} -.tone-14 { - background-color: #83929F; -} -.tone-15 { - background-color: #A3ADB8; -} - +.tf-nc { + position: relative; + width: 250px; + text-align: center; + background-color: #fff100; +} \ No newline at end of file From 5cd37eeed1483e7113029ab9a419f9d74afccec3 Mon Sep 17 00:00:00 2001 From: Tishj Date: Mon, 3 Jul 2023 16:12:51 +0200 Subject: [PATCH 07/26] fix #7999 throw error if prepared parameters are used in a multi-statement 'execute' call --- tools/pythonpkg/src/pyconnection.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/pythonpkg/src/pyconnection.cpp b/tools/pythonpkg/src/pyconnection.cpp index 26b9331e5b1e..4485989f045a 100644 --- a/tools/pythonpkg/src/pyconnection.cpp +++ b/tools/pythonpkg/src/pyconnection.cpp @@ -47,6 +47,7 @@ #include "duckdb_python/pybind11/conversions/exception_handling_enum.hpp" #include "duckdb/parser/parsed_data/drop_info.hpp" #include "duckdb/catalog/catalog_entry/scalar_function_catalog_entry.hpp" +#include "duckdb/main/pending_query_result.hpp" #include @@ -442,8 +443,15 @@ unique_ptr DuckDBPyConnection::ExecuteInternal(const string &query, // if there are multiple statements, we directly execute the statements besides the last one // we only return the result of the last statement to the user, unless one of the previous statements fails for (idx_t i = 0; i + 1 < statements.size(); i++) { - auto pending_query = connection->PendingQuery(std::move(statements[i]), false); - auto res = CompletePendingQuery(*pending_query); + if (statements[i]->n_param != 0) { + throw NotImplementedException( + "Prepared parameters are only supported for the last statement, please split your query up into " + "separate 'execute' calls if you want to use prepared parameters"); + } + auto pending_query_p = connection->PendingQuery(std::move(statements[i]), false); + D_ASSERT(pending_query_p->type == QueryResultType::PENDING_RESULT); + auto &pending_query = dynamic_cast(*pending_query_p); + auto res = CompletePendingQuery(pending_query); if (res->HasError()) { res->ThrowError(); From 902030f368db7628c6eb7160b1df6c1beaedb2c6 Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Mon, 2 Oct 2023 10:57:20 +0200 Subject: [PATCH 08/26] renaming and moving around, but can no longer import duckdb --- scripts/generate_querygraph.py | 5 ++++- .../{ => duckdb-stubs}/duckdb_query_graph/__init__.py | 0 .../{ => duckdb-stubs}/duckdb_query_graph/query_graph.css | 0 tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py | 7 +++++++ tools/pythonpkg/setup.py | 2 ++ 5 files changed, 13 insertions(+), 1 deletion(-) rename tools/pythonpkg/{ => duckdb-stubs}/duckdb_query_graph/__init__.py (100%) rename tools/pythonpkg/{ => duckdb-stubs}/duckdb_query_graph/query_graph.css (100%) create mode 100644 tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py diff --git a/scripts/generate_querygraph.py b/scripts/generate_querygraph.py index c7dcfaa7ba11..96263b2267f7 100644 --- a/scripts/generate_querygraph.py +++ b/scripts/generate_querygraph.py @@ -4,11 +4,14 @@ import os import sys +from functools import reduce from python_helpers import open_utf8 +from duckdb.duckdb_query_graph import generate + sys.path.insert(0, 'benchmark') -from tools.pythonpkg.duckdb_query_graph import generate +from duckdb_query_graph import generate arguments = sys.argv if len(arguments) <= 1: diff --git a/tools/pythonpkg/duckdb_query_graph/__init__.py b/tools/pythonpkg/duckdb-stubs/duckdb_query_graph/__init__.py similarity index 100% rename from tools/pythonpkg/duckdb_query_graph/__init__.py rename to tools/pythonpkg/duckdb-stubs/duckdb_query_graph/__init__.py diff --git a/tools/pythonpkg/duckdb_query_graph/query_graph.css b/tools/pythonpkg/duckdb-stubs/duckdb_query_graph/query_graph.css similarity index 100% rename from tools/pythonpkg/duckdb_query_graph/query_graph.css rename to tools/pythonpkg/duckdb-stubs/duckdb_query_graph/query_graph.css diff --git a/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py b/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py new file mode 100644 index 000000000000..f3f38b86a309 --- /dev/null +++ b/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py @@ -0,0 +1,7 @@ +from duckdb.duckdb.duckdb_query_graph import ( + generate +) + +__all__ = [ + "generate" +] diff --git a/tools/pythonpkg/setup.py b/tools/pythonpkg/setup.py index fdf2911019cc..75770ff46878 100644 --- a/tools/pythonpkg/setup.py +++ b/tools/pythonpkg/setup.py @@ -319,9 +319,11 @@ def setup_data_files(data_files): packages = [ lib_name, 'duckdb.typing', + 'duckdb.duckdb_query_graph', 'duckdb.functional', 'duckdb.value', 'duckdb-stubs', + 'duckdb-stubs.duckdb_query_graph', 'duckdb-stubs.functional', 'duckdb-stubs.typing', 'adbc_driver_duckdb', From 5a6974a58f2a39cd3b2d5f35c0c9da321826993a Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Mon, 2 Oct 2023 11:06:05 +0200 Subject: [PATCH 09/26] remove duckdb_query_graph from python building --- tools/pythonpkg/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/pythonpkg/setup.py b/tools/pythonpkg/setup.py index 75770ff46878..d04a00d1ee28 100644 --- a/tools/pythonpkg/setup.py +++ b/tools/pythonpkg/setup.py @@ -319,11 +319,11 @@ def setup_data_files(data_files): packages = [ lib_name, 'duckdb.typing', - 'duckdb.duckdb_query_graph', + # 'duckdb.duckdb_query_graph', 'duckdb.functional', 'duckdb.value', 'duckdb-stubs', - 'duckdb-stubs.duckdb_query_graph', + # 'duckdb-stubs.duckdb_query_graph', 'duckdb-stubs.functional', 'duckdb-stubs.typing', 'adbc_driver_duckdb', From d7a6178e4328d9a5fa1e6a8acf9625dc668358a2 Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Mon, 2 Oct 2023 14:00:20 +0200 Subject: [PATCH 10/26] script works again. pure css --- scripts/generate_querygraph.py | 2 - .../duckdb_query_graph/__init__.py | 226 --------------- tools/pythonpkg/duckdb/__init__.py | 4 +- .../duckdb/duckdb_query_graph/__init__.py | 270 +++++++++++++++++- .../duckdb_query_graph/query_graph.css | 0 tools/pythonpkg/setup.py | 3 +- 6 files changed, 268 insertions(+), 237 deletions(-) delete mode 100644 tools/pythonpkg/duckdb-stubs/duckdb_query_graph/__init__.py rename tools/pythonpkg/{duckdb-stubs => duckdb}/duckdb_query_graph/query_graph.css (100%) diff --git a/scripts/generate_querygraph.py b/scripts/generate_querygraph.py index 96263b2267f7..5ab84f15de5e 100644 --- a/scripts/generate_querygraph.py +++ b/scripts/generate_querygraph.py @@ -11,8 +11,6 @@ sys.path.insert(0, 'benchmark') -from duckdb_query_graph import generate - arguments = sys.argv if len(arguments) <= 1: print("Usage: python generate_querygraph.py [input.json] [output.html] [open={1,0}]") diff --git a/tools/pythonpkg/duckdb-stubs/duckdb_query_graph/__init__.py b/tools/pythonpkg/duckdb-stubs/duckdb_query_graph/__init__.py deleted file mode 100644 index 8615aa59f80b..000000000000 --- a/tools/pythonpkg/duckdb-stubs/duckdb_query_graph/__init__.py +++ /dev/null @@ -1,226 +0,0 @@ -import json -import os -from functools import reduce - -qgraph_css = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'query_graph.css') - -class NodeTiming: - - def __init__(self, phase, time): - self.phase = phase - self.time = time - # percentage is determined later. - self.percentage = 0 - - def calculate_percentage(self, total_time): - self.percentage = self.time/total_time - - def combine_timing(l, r): - # TODO: can only add timings for same-phase nodes - total_time = l.time + r.time - return NodeTiming(l.phase, total_time) - -class AllTimings: - - def __init__(self): - self.phase_to_timings = {} - - def add_node_timing(self, node_timing): - if node_timing.phase in self.phase_to_timings: - self.phase_to_timings[node_timing.phase].append(node_timing) - return - self.phase_to_timings[node_timing.phase] = [node_timing] - - def get_phase_timings(self, phase): - return self.phase_to_timings[phase] - - def get_summary_phase_timings(self, phase): - return reduce(NodeTiming.combine_timing, self.phase_to_timings[phase]) - - def get_phases_high_to_low(self): - phases = list(self.phase_to_timings.keys()) - phases.sort(key=lambda x: (self.get_summary_phase_timings(x)).time) - phases.reverse() - return phases - - def get_sum_of_all_timings(self): - total_timing_sum = 0 - for phase in self.phase_to_timings.keys(): - total_timing_sum += self.get_summary_phase_timings(phase).time - return total_timing_sum - -QUERY_TIMINGS = AllTimings() - -def open_utf8(fpath, flags): - import sys - if sys.version_info[0] < 3: - return open(fpath, flags) - else: - return open(fpath, flags, encoding="utf8") - - -# if detailed profiling is enabled, then information on how long the optimizer/physical planner etc. is available -# that is "top level timing information" -def get_top_level_timings(json): - return [] - -def get_child_timings(top_node): - global QUERY_TIMINGS - node_timing = NodeTiming(top_node['name'], float(top_node['timing'])) - QUERY_TIMINGS.add_node_timing(node_timing) - for child in top_node['children']: - get_child_timings(child) - -def get_node_body(name, result, cardinality, extra_info, timing): - body = "" - body += "
    " - body += f"

    {name} ({result}s)

    " - body += f"

    cardinality = {cardinality}

    " - if extra_info: - extra_info = extra_info.replace("[INFOSEPARATOR]", "----") - extra_info = extra_info.replace("

    ", "
    ----
    ") - body += f"

    {extra_info}

    " - # TODO: Expand on timing. Usually available from a detailed profiling - body += "
    " - body += "
    " - return body - - -def generate_tree_recursive(json_graph): - node_prefix_html = "
  • " - node_suffix_html = "
  • " - node_body = get_node_body(json_graph["name"], - json_graph["timing"], - json_graph["cardinality"], - json_graph["extra_info"].replace("\n", "
    "), - json_graph["timings"]) - - children_html = "" - if len(json_graph['children']) >= 1: - children_html += "
      " - for child in json_graph["children"]: - children_html += generate_tree_recursive(child) - children_html += "
    " - return node_prefix_html + node_body + children_html + node_suffix_html - -# For generating the table in the top left. -def generate_timing_html(graph_json): - global QUERY_TIMINGS - json_graph = json.loads(graph_json) - gather_timing_information(json_graph) - total_time = float(json_graph['timing']) - table_head = """ - - - - - - - - """ - - table_body = "" - table_end = "
    PhaseTimePercentage
    " - - execution_time = QUERY_TIMINGS.get_sum_of_all_timings() - - all_phases = QUERY_TIMINGS.get_phases_high_to_low() - QUERY_TIMINGS.add_node_timing(NodeTiming("TOTAL TIME", total_time)) - QUERY_TIMINGS.add_node_timing(NodeTiming("Execution Time", execution_time)) - all_phases = ["TOTAL TIME", "Execution Time"] + all_phases - for phase in all_phases: - summarized_phase = QUERY_TIMINGS.get_summary_phase_timings(phase) - summarized_phase.calculate_percentage(total_time) - phase_column = f"{phase}" if phase == "TOTAL TIME" or phase == "Execution Time" else phase - table_body += f""" - - {phase_column} - {summarized_phase.time} - {str(summarized_phase.percentage * 100)[:6]}% - -""" - table_body += table_end - return table_head + table_body - -def generate_tree_html(graph_json): - json_graph = json.loads(graph_json) - tree_prefix = "
    \n
      " - tree_suffix = "
    " - # first level of json is general overview - # FIXME: make sure json output first level always has only 1 level - tree_body = generate_tree_recursive(json_graph['children'][0]) - return tree_prefix + tree_body + tree_suffix - - -def generate_ipython(json_input): - from IPython.core.display import HTML - - html_output = generate_html(json_input, False) - - return HTML(""" - ${CSS} - ${LIBRARIES} -
    - ${CHART_SCRIPT} - """.replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', html_output['chart_script']).replace('${LIBRARIES}', html_output['libraries'])) - - -def generate_style_html(graph_json, include_meta_info): - treeflex_css = "\n" - css = "\n" - return { - 'treeflex_css': treeflex_css, - 'duckdb_css': css, - 'libraries': '', - 'chart_script': '' - } - -def gather_timing_information(json): - # add up all of the times - # measure each time as a percentage of the total time. - # then you can return a list of [phase, time, percentage] - top_level_timings = get_top_level_timings(json) - child_timings = get_child_timings(json['children'][0]) - -def generate(input_file, output_file): - with open_utf8(input_file, 'r') as f: - text = f.read() - - html_output = generate_style_html(text, True) - timing_table = generate_timing_html(text) - tree_output = generate_tree_html(text) - - # finally create and write the html - with open_utf8(output_file, "w+") as f: - html = """ - - - - - Query Profile Graph for Query - ${TREEFLEX_CSS} - - - -
    -
    - ${TIMING_TABLE} -
    - ${TREE} - - -""" - html = html.replace("${TREEFLEX_CSS}", html_output['treeflex_css']) - html = html.replace("${DUCKDB_CSS}", html_output['duckdb_css']) - html = html.replace("${TIMING_TABLE}", timing_table) - html = html.replace('${TREE}', tree_output) - f.write(html) - - -output = "output_detailed.json" -generate("output_detailed.json", "output_detailed.html") diff --git a/tools/pythonpkg/duckdb/__init__.py b/tools/pythonpkg/duckdb/__init__.py index 440accf0e402..3b7c3704c60f 100644 --- a/tools/pythonpkg/duckdb/__init__.py +++ b/tools/pythonpkg/duckdb/__init__.py @@ -3,9 +3,11 @@ # Modules import duckdb.functional as functional import duckdb.typing as typing +import duckdb.duckdb_query_graph as duckdb_query_graph _exported_symbols.extend([ "typing", - "functional" + "functional", + "duckdb_query_graph" ]) # Classes diff --git a/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py b/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py index f3f38b86a309..d2a96791b9fb 100644 --- a/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py +++ b/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py @@ -1,7 +1,265 @@ -from duckdb.duckdb.duckdb_query_graph import ( - generate -) +import json +import os +from functools import reduce -__all__ = [ - "generate" -] +qgraph_css = """ +.styled-table { + border-collapse: collapse; + margin: 25px 0; + font-size: 0.9em; + font-family: sans-serif; + min-width: 400px; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); +} +.styled-table thead tr { + background-color: #009879; + color: #ffffff; + text-align: left; +} +.styled-table th, +.styled-table td { + padding: 12px 15px; +} +.styled-table tbody tr { + border-bottom: 1px solid #dddddd; +} + +.styled-table tbody tr:nth-of-type(even) { + background-color: #f3f3f3; +} + +.styled-table tbody tr:last-of-type { + border-bottom: 2px solid #009879; +} + +.node-body { + font-size:15px; +} +.tf-nc { + position: relative; + width: 250px; + text-align: center; + background-color: #fff100; +}""" + +class NodeTiming: + + def __init__(self, phase, time): + self.phase = phase + self.time = time + # percentage is determined later. + self.percentage = 0 + + def calculate_percentage(self, total_time): + self.percentage = self.time/total_time + + def combine_timing(l, r): + # TODO: can only add timings for same-phase nodes + total_time = l.time + r.time + return NodeTiming(l.phase, total_time) + +class AllTimings: + + def __init__(self): + self.phase_to_timings = {} + + def add_node_timing(self, node_timing): + if node_timing.phase in self.phase_to_timings: + self.phase_to_timings[node_timing.phase].append(node_timing) + return + self.phase_to_timings[node_timing.phase] = [node_timing] + + def get_phase_timings(self, phase): + return self.phase_to_timings[phase] + + def get_summary_phase_timings(self, phase): + return reduce(NodeTiming.combine_timing, self.phase_to_timings[phase]) + + def get_phases_high_to_low(self): + phases = list(self.phase_to_timings.keys()) + phases.sort(key=lambda x: (self.get_summary_phase_timings(x)).time) + phases.reverse() + return phases + + def get_sum_of_all_timings(self): + total_timing_sum = 0 + for phase in self.phase_to_timings.keys(): + total_timing_sum += self.get_summary_phase_timings(phase).time + return total_timing_sum + +QUERY_TIMINGS = AllTimings() + +def open_utf8(fpath, flags): + import sys + if sys.version_info[0] < 3: + return open(fpath, flags) + else: + return open(fpath, flags, encoding="utf8") + + +# if detailed profiling is enabled, then information on how long the optimizer/physical planner etc. is available +# that is "top level timing information" +def get_top_level_timings(json): + return [] + +def get_child_timings(top_node): + global QUERY_TIMINGS + node_timing = NodeTiming(top_node['name'], float(top_node['timing'])) + QUERY_TIMINGS.add_node_timing(node_timing) + for child in top_node['children']: + get_child_timings(child) + +def get_node_body(name, result, cardinality, extra_info, timing): + body = "" + body += "
    " + body += f"

    {name} ({result}s)

    " + body += f"

    cardinality = {cardinality}

    " + if extra_info: + extra_info = extra_info.replace("[INFOSEPARATOR]", "----") + extra_info = extra_info.replace("

    ", "
    ----
    ") + body += f"

    {extra_info}

    " + # TODO: Expand on timing. Usually available from a detailed profiling + body += "
    " + body += "
    " + return body + + +def generate_tree_recursive(json_graph): + node_prefix_html = "
  • " + node_suffix_html = "
  • " + node_body = get_node_body(json_graph["name"], + json_graph["timing"], + json_graph["cardinality"], + json_graph["extra_info"].replace("\n", "
    "), + json_graph["timings"]) + + children_html = "" + if len(json_graph['children']) >= 1: + children_html += "
      " + for child in json_graph["children"]: + children_html += generate_tree_recursive(child) + children_html += "
    " + return node_prefix_html + node_body + children_html + node_suffix_html + +# For generating the table in the top left. +def generate_timing_html(graph_json): + global QUERY_TIMINGS + json_graph = json.loads(graph_json) + gather_timing_information(json_graph) + total_time = float(json_graph['timing']) + table_head = """ + + + + + + + + """ + + table_body = "" + table_end = "
    PhaseTimePercentage
    " + + execution_time = QUERY_TIMINGS.get_sum_of_all_timings() + + all_phases = QUERY_TIMINGS.get_phases_high_to_low() + QUERY_TIMINGS.add_node_timing(NodeTiming("TOTAL TIME", total_time)) + QUERY_TIMINGS.add_node_timing(NodeTiming("Execution Time", execution_time)) + all_phases = ["TOTAL TIME", "Execution Time"] + all_phases + for phase in all_phases: + summarized_phase = QUERY_TIMINGS.get_summary_phase_timings(phase) + summarized_phase.calculate_percentage(total_time) + phase_column = f"{phase}" if phase == "TOTAL TIME" or phase == "Execution Time" else phase + table_body += f""" + + {phase_column} + {summarized_phase.time} + {str(summarized_phase.percentage * 100)[:6]}% + +""" + table_body += table_end + return table_head + table_body + +def generate_tree_html(graph_json): + json_graph = json.loads(graph_json) + tree_prefix = "
    \n
      " + tree_suffix = "
    " + # first level of json is general overview + # FIXME: make sure json output first level always has only 1 level + tree_body = generate_tree_recursive(json_graph['children'][0]) + return tree_prefix + tree_body + tree_suffix + + +def generate_ipython(json_input): + from IPython.core.display import HTML + + html_output = generate_html(json_input, False) + + return HTML(""" + ${CSS} + ${LIBRARIES} +
    + ${CHART_SCRIPT} + """.replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', html_output['chart_script']).replace('${LIBRARIES}', html_output['libraries'])) + + +def generate_style_html(graph_json, include_meta_info): + treeflex_css = "\n" + css = "\n" + return { + 'treeflex_css': treeflex_css, + 'duckdb_css': css, + 'libraries': '', + 'chart_script': '' + } + +def gather_timing_information(json): + # add up all of the times + # measure each time as a percentage of the total time. + # then you can return a list of [phase, time, percentage] + top_level_timings = get_top_level_timings(json) + child_timings = get_child_timings(json['children'][0]) + +def generate(input_file, output_file): + with open_utf8(input_file, 'r') as f: + text = f.read() + + html_output = generate_style_html(text, True) + timing_table = generate_timing_html(text) + tree_output = generate_tree_html(text) + + # finally create and write the html + with open_utf8(output_file, "w+") as f: + html = """ + + + + + Query Profile Graph for Query + ${TREEFLEX_CSS} + + + +
    +
    + ${TIMING_TABLE} +
    + ${TREE} + + +""" + html = html.replace("${TREEFLEX_CSS}", html_output['treeflex_css']) + html = html.replace("${DUCKDB_CSS}", html_output['duckdb_css']) + html = html.replace("${TIMING_TABLE}", timing_table) + html = html.replace('${TREE}', tree_output) + f.write(html) + + +# output = "output_detailed.json" +# generate("output_detailed.json", "output_detailed.html") + +__all__ = ["generate"] \ No newline at end of file diff --git a/tools/pythonpkg/duckdb-stubs/duckdb_query_graph/query_graph.css b/tools/pythonpkg/duckdb/duckdb_query_graph/query_graph.css similarity index 100% rename from tools/pythonpkg/duckdb-stubs/duckdb_query_graph/query_graph.css rename to tools/pythonpkg/duckdb/duckdb_query_graph/query_graph.css diff --git a/tools/pythonpkg/setup.py b/tools/pythonpkg/setup.py index d04a00d1ee28..b925bab18917 100644 --- a/tools/pythonpkg/setup.py +++ b/tools/pythonpkg/setup.py @@ -319,11 +319,10 @@ def setup_data_files(data_files): packages = [ lib_name, 'duckdb.typing', - # 'duckdb.duckdb_query_graph', + 'duckdb.duckdb_query_graph', 'duckdb.functional', 'duckdb.value', 'duckdb-stubs', - # 'duckdb-stubs.duckdb_query_graph', 'duckdb-stubs.functional', 'duckdb-stubs.typing', 'adbc_driver_duckdb', From 623f146334e9b8b8d0894e939614e5944ce42650 Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Mon, 2 Oct 2023 14:48:07 +0200 Subject: [PATCH 11/26] some changes to get nicer colors. might need to add more colors for other operators --- .../duckdb/duckdb_query_graph/__init__.py | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py b/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py index d2a96791b9fb..1bf59509f0d0 100644 --- a/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py +++ b/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py @@ -40,7 +40,9 @@ width: 250px; text-align: center; background-color: #fff100; -}""" +} +""" + class NodeTiming: @@ -109,10 +111,34 @@ def get_child_timings(top_node): for child in top_node['children']: get_child_timings(child) +color_map = { + "HASH_JOIN": "#ffffba", + "PROJECTION": "#ffb3ba", + "SEQ_SCAN": "#baffc9", + "UNGROUPED_AGGREGATE": "#ffdfba", + "FILTER": "#bae1ff" +} + def get_node_body(name, result, cardinality, extra_info, timing): - body = "" + node_class = "" + + if name == "SEQ_SCAN": + node_class = "seq_scan" + if name == "HASH_JOIN": + node_class = "hash_join" + if name == "PROJECTION": + node_class = "projection" + if name == "UNGROUPED_AGGREGATE": + node_class = "ungrouped_aggregate" + node_style = "" + stripped_name = name.strip() + if stripped_name in color_map: + node_style = f"background-color: {color_map[stripped_name]};" + + body = f"" body += "
    " - body += f"

    {name} ({result}s)

    " + new_name = name.replace("_", " ") + body += f"

    {new_name} ({result}s)

    " body += f"

    cardinality = {cardinality}

    " if extra_info: extra_info = extra_info.replace("[INFOSEPARATOR]", "----") From fb6cd9be6228892c6f1eafcd9ff9019cdc7a557b Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Mon, 2 Oct 2023 16:23:46 +0200 Subject: [PATCH 12/26] new colors for more operators. delete the css --- .../duckdb/duckdb_query_graph/__init__.py | 17 +++++---- .../duckdb/duckdb_query_graph/query_graph.css | 38 ------------------- 2 files changed, 10 insertions(+), 45 deletions(-) delete mode 100644 tools/pythonpkg/duckdb/duckdb_query_graph/query_graph.css diff --git a/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py b/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py index 1bf59509f0d0..d91f56a6c340 100644 --- a/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py +++ b/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py @@ -77,7 +77,7 @@ def get_phase_timings(self, phase): def get_summary_phase_timings(self, phase): return reduce(NodeTiming.combine_timing, self.phase_to_timings[phase]) - def get_phases_high_to_low(self): + def get_phases(self): phases = list(self.phase_to_timings.keys()) phases.sort(key=lambda x: (self.get_summary_phase_timings(x)).time) phases.reverse() @@ -99,7 +99,7 @@ def open_utf8(fpath, flags): return open(fpath, flags, encoding="utf8") -# if detailed profiling is enabled, then information on how long the optimizer/physical planner etc. is available +# if detailed profiling is enabled, then information on the timing of each stage of the query is available # that is "top level timing information" def get_top_level_timings(json): return [] @@ -116,7 +116,13 @@ def get_child_timings(top_node): "PROJECTION": "#ffb3ba", "SEQ_SCAN": "#baffc9", "UNGROUPED_AGGREGATE": "#ffdfba", - "FILTER": "#bae1ff" + "FILTER": "#bae1ff", + "ORDER_BY": "#facd60", + "PERFECT_HASH_GROUP_BY": "#ffffba", + "HASH_GROUP_BY": "#ffffba", + "NESTED_LOOP_JOIN": "#ffffba", + "STREAMING_LIMIT": "#facd60", + "COLUMN_DATA_SCAN": "#1ac0c6" } def get_node_body(name, result, cardinality, extra_info, timing): @@ -188,7 +194,7 @@ def generate_timing_html(graph_json): execution_time = QUERY_TIMINGS.get_sum_of_all_timings() - all_phases = QUERY_TIMINGS.get_phases_high_to_low() + all_phases = QUERY_TIMINGS.get_phases() QUERY_TIMINGS.add_node_timing(NodeTiming("TOTAL TIME", total_time)) QUERY_TIMINGS.add_node_timing(NodeTiming("Execution Time", execution_time)) all_phases = ["TOTAL TIME", "Execution Time"] + all_phases @@ -285,7 +291,4 @@ def generate(input_file, output_file): f.write(html) -# output = "output_detailed.json" -# generate("output_detailed.json", "output_detailed.html") - __all__ = ["generate"] \ No newline at end of file diff --git a/tools/pythonpkg/duckdb/duckdb_query_graph/query_graph.css b/tools/pythonpkg/duckdb/duckdb_query_graph/query_graph.css deleted file mode 100644 index 4faffdbcab0f..000000000000 --- a/tools/pythonpkg/duckdb/duckdb_query_graph/query_graph.css +++ /dev/null @@ -1,38 +0,0 @@ -.styled-table { - border-collapse: collapse; - margin: 25px 0; - font-size: 0.9em; - font-family: sans-serif; - min-width: 400px; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); -} -.styled-table thead tr { - background-color: #009879; - color: #ffffff; - text-align: left; -} -.styled-table th, -.styled-table td { - padding: 12px 15px; -} -.styled-table tbody tr { - border-bottom: 1px solid #dddddd; -} - -.styled-table tbody tr:nth-of-type(even) { - background-color: #f3f3f3; -} - -.styled-table tbody tr:last-of-type { - border-bottom: 2px solid #009879; -} - -.node-body { - font-size:15px; -} -.tf-nc { - position: relative; - width: 250px; - text-align: center; - background-color: #fff100; -} \ No newline at end of file From fa773c3002f64c2e138b6be2196d6f2545d7ccab Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Mon, 2 Oct 2023 16:47:39 +0200 Subject: [PATCH 13/26] format-fix --- scripts/generate_querygraph.py | 34 +++++++++---------- .../duckdb/duckdb_query_graph/__init__.py | 12 +------ 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/scripts/generate_querygraph.py b/scripts/generate_querygraph.py index 5ab84f15de5e..0c1fd63fcad3 100644 --- a/scripts/generate_querygraph.py +++ b/scripts/generate_querygraph.py @@ -13,33 +13,33 @@ arguments = sys.argv if len(arguments) <= 1: - print("Usage: python generate_querygraph.py [input.json] [output.html] [open={1,0}]") - exit(1) + print("Usage: python generate_querygraph.py [input.json] [output.html] [open={1,0}]") + exit(1) input = arguments[1] if len(arguments) <= 2: - if ".json" in input: - output = input.replace(".json", ".html") - else: - output = input + ".html" + if ".json" in input: + output = input.replace(".json", ".html") + else: + output = input + ".html" else: - output = arguments[2] + output = arguments[2] open_output = True if len(arguments) >= 4: - open_arg = arguments[3].lower().replace('open=', '') - if open_arg == "1" or open_arg == "true": - open_output = True - elif open_arg == "0" or open_arg == "false": - open_output = False - else: - print("Incorrect input for open_output, expected TRUE or FALSE") - exit(1) + open_arg = arguments[3].lower().replace('open=', '') + if open_arg == "1" or open_arg == "true": + open_output = True + elif open_arg == "0" or open_arg == "false": + open_output = False + else: + print("Incorrect input for open_output, expected TRUE or FALSE") + exit(1) generate(input, output) with open(output, 'r') as f: - text = f.read() + text = f.read() if open_output: - os.system('open "' + output.replace('"', '\\"') + '"') \ No newline at end of file + os.system('open "' + output.replace('"', '\\"') + '"') diff --git a/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py b/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py index d91f56a6c340..2960222ebd5d 100644 --- a/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py +++ b/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py @@ -126,22 +126,12 @@ def get_child_timings(top_node): } def get_node_body(name, result, cardinality, extra_info, timing): - node_class = "" - - if name == "SEQ_SCAN": - node_class = "seq_scan" - if name == "HASH_JOIN": - node_class = "hash_join" - if name == "PROJECTION": - node_class = "projection" - if name == "UNGROUPED_AGGREGATE": - node_class = "ungrouped_aggregate" node_style = "" stripped_name = name.strip() if stripped_name in color_map: node_style = f"background-color: {color_map[stripped_name]};" - body = f"" + body = f"" body += "
    " new_name = name.replace("_", " ") body += f"

    {new_name} ({result}s)

    " From 48e1b135057ecfce0a66980c69b6ece88a4af3ec Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Tue, 3 Oct 2023 13:53:10 +0200 Subject: [PATCH 14/26] new query graph tool --- tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py b/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py index 2960222ebd5d..cddd81fab4f1 100644 --- a/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py +++ b/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py @@ -122,7 +122,8 @@ def get_child_timings(top_node): "HASH_GROUP_BY": "#ffffba", "NESTED_LOOP_JOIN": "#ffffba", "STREAMING_LIMIT": "#facd60", - "COLUMN_DATA_SCAN": "#1ac0c6" + "COLUMN_DATA_SCAN": "#1ac0c6", + "TOP_N": "#ffdfba" } def get_node_body(name, result, cardinality, extra_info, timing): @@ -135,11 +136,11 @@ def get_node_body(name, result, cardinality, extra_info, timing): body += "
    " new_name = name.replace("_", " ") body += f"

    {new_name} ({result}s)

    " - body += f"

    cardinality = {cardinality}

    " if extra_info: extra_info = extra_info.replace("[INFOSEPARATOR]", "----") - extra_info = extra_info.replace("

    ", "
    ----
    ") + extra_info = extra_info.replace("

    ", "
    ") body += f"

    {extra_info}

    " + body += f"

    cardinality = {cardinality}

    " # TODO: Expand on timing. Usually available from a detailed profiling body += "
    " body += "" From 7ccf53bf64921192c9e9f6cb31874b8e738476ab Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Fri, 6 Oct 2023 11:17:38 +0200 Subject: [PATCH 15/26] PR review comments. query_graph is exceutable module. use argparse, no globals --- scripts/generate_querygraph.py | 2 +- tools/pythonpkg/duckdb/__init__.py | 4 +- .../duckdb/duckdb_query_graph/__init__.py | 285 ---------------- .../pythonpkg/duckdb/query_graph/__main__.py | 323 ++++++++++++++++++ tools/pythonpkg/setup.py | 2 +- 5 files changed, 327 insertions(+), 289 deletions(-) delete mode 100644 tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py create mode 100644 tools/pythonpkg/duckdb/query_graph/__main__.py diff --git a/scripts/generate_querygraph.py b/scripts/generate_querygraph.py index 0c1fd63fcad3..0b19b233bf6c 100644 --- a/scripts/generate_querygraph.py +++ b/scripts/generate_querygraph.py @@ -7,7 +7,7 @@ from functools import reduce from python_helpers import open_utf8 -from duckdb.duckdb_query_graph import generate +from duckdb.query_graph import generate sys.path.insert(0, 'benchmark') diff --git a/tools/pythonpkg/duckdb/__init__.py b/tools/pythonpkg/duckdb/__init__.py index 3b7c3704c60f..1febbb1d07a6 100644 --- a/tools/pythonpkg/duckdb/__init__.py +++ b/tools/pythonpkg/duckdb/__init__.py @@ -3,11 +3,11 @@ # Modules import duckdb.functional as functional import duckdb.typing as typing -import duckdb.duckdb_query_graph as duckdb_query_graph +import duckdb.query_graph as query_graph _exported_symbols.extend([ "typing", "functional", - "duckdb_query_graph" + "query_graph" ]) # Classes diff --git a/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py b/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py deleted file mode 100644 index cddd81fab4f1..000000000000 --- a/tools/pythonpkg/duckdb/duckdb_query_graph/__init__.py +++ /dev/null @@ -1,285 +0,0 @@ -import json -import os -from functools import reduce - -qgraph_css = """ -.styled-table { - border-collapse: collapse; - margin: 25px 0; - font-size: 0.9em; - font-family: sans-serif; - min-width: 400px; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); -} -.styled-table thead tr { - background-color: #009879; - color: #ffffff; - text-align: left; -} -.styled-table th, -.styled-table td { - padding: 12px 15px; -} -.styled-table tbody tr { - border-bottom: 1px solid #dddddd; -} - -.styled-table tbody tr:nth-of-type(even) { - background-color: #f3f3f3; -} - -.styled-table tbody tr:last-of-type { - border-bottom: 2px solid #009879; -} - -.node-body { - font-size:15px; -} -.tf-nc { - position: relative; - width: 250px; - text-align: center; - background-color: #fff100; -} -""" - - -class NodeTiming: - - def __init__(self, phase, time): - self.phase = phase - self.time = time - # percentage is determined later. - self.percentage = 0 - - def calculate_percentage(self, total_time): - self.percentage = self.time/total_time - - def combine_timing(l, r): - # TODO: can only add timings for same-phase nodes - total_time = l.time + r.time - return NodeTiming(l.phase, total_time) - -class AllTimings: - - def __init__(self): - self.phase_to_timings = {} - - def add_node_timing(self, node_timing): - if node_timing.phase in self.phase_to_timings: - self.phase_to_timings[node_timing.phase].append(node_timing) - return - self.phase_to_timings[node_timing.phase] = [node_timing] - - def get_phase_timings(self, phase): - return self.phase_to_timings[phase] - - def get_summary_phase_timings(self, phase): - return reduce(NodeTiming.combine_timing, self.phase_to_timings[phase]) - - def get_phases(self): - phases = list(self.phase_to_timings.keys()) - phases.sort(key=lambda x: (self.get_summary_phase_timings(x)).time) - phases.reverse() - return phases - - def get_sum_of_all_timings(self): - total_timing_sum = 0 - for phase in self.phase_to_timings.keys(): - total_timing_sum += self.get_summary_phase_timings(phase).time - return total_timing_sum - -QUERY_TIMINGS = AllTimings() - -def open_utf8(fpath, flags): - import sys - if sys.version_info[0] < 3: - return open(fpath, flags) - else: - return open(fpath, flags, encoding="utf8") - - -# if detailed profiling is enabled, then information on the timing of each stage of the query is available -# that is "top level timing information" -def get_top_level_timings(json): - return [] - -def get_child_timings(top_node): - global QUERY_TIMINGS - node_timing = NodeTiming(top_node['name'], float(top_node['timing'])) - QUERY_TIMINGS.add_node_timing(node_timing) - for child in top_node['children']: - get_child_timings(child) - -color_map = { - "HASH_JOIN": "#ffffba", - "PROJECTION": "#ffb3ba", - "SEQ_SCAN": "#baffc9", - "UNGROUPED_AGGREGATE": "#ffdfba", - "FILTER": "#bae1ff", - "ORDER_BY": "#facd60", - "PERFECT_HASH_GROUP_BY": "#ffffba", - "HASH_GROUP_BY": "#ffffba", - "NESTED_LOOP_JOIN": "#ffffba", - "STREAMING_LIMIT": "#facd60", - "COLUMN_DATA_SCAN": "#1ac0c6", - "TOP_N": "#ffdfba" -} - -def get_node_body(name, result, cardinality, extra_info, timing): - node_style = "" - stripped_name = name.strip() - if stripped_name in color_map: - node_style = f"background-color: {color_map[stripped_name]};" - - body = f"" - body += "
    " - new_name = name.replace("_", " ") - body += f"

    {new_name} ({result}s)

    " - if extra_info: - extra_info = extra_info.replace("[INFOSEPARATOR]", "----") - extra_info = extra_info.replace("

    ", "
    ") - body += f"

    {extra_info}

    " - body += f"

    cardinality = {cardinality}

    " - # TODO: Expand on timing. Usually available from a detailed profiling - body += "
    " - body += "
    " - return body - - -def generate_tree_recursive(json_graph): - node_prefix_html = "
  • " - node_suffix_html = "
  • " - node_body = get_node_body(json_graph["name"], - json_graph["timing"], - json_graph["cardinality"], - json_graph["extra_info"].replace("\n", "
    "), - json_graph["timings"]) - - children_html = "" - if len(json_graph['children']) >= 1: - children_html += "
      " - for child in json_graph["children"]: - children_html += generate_tree_recursive(child) - children_html += "
    " - return node_prefix_html + node_body + children_html + node_suffix_html - -# For generating the table in the top left. -def generate_timing_html(graph_json): - global QUERY_TIMINGS - json_graph = json.loads(graph_json) - gather_timing_information(json_graph) - total_time = float(json_graph['timing']) - table_head = """ - - - - - - - - """ - - table_body = "" - table_end = "
    PhaseTimePercentage
    " - - execution_time = QUERY_TIMINGS.get_sum_of_all_timings() - - all_phases = QUERY_TIMINGS.get_phases() - QUERY_TIMINGS.add_node_timing(NodeTiming("TOTAL TIME", total_time)) - QUERY_TIMINGS.add_node_timing(NodeTiming("Execution Time", execution_time)) - all_phases = ["TOTAL TIME", "Execution Time"] + all_phases - for phase in all_phases: - summarized_phase = QUERY_TIMINGS.get_summary_phase_timings(phase) - summarized_phase.calculate_percentage(total_time) - phase_column = f"{phase}" if phase == "TOTAL TIME" or phase == "Execution Time" else phase - table_body += f""" - - {phase_column} - {summarized_phase.time} - {str(summarized_phase.percentage * 100)[:6]}% - -""" - table_body += table_end - return table_head + table_body - -def generate_tree_html(graph_json): - json_graph = json.loads(graph_json) - tree_prefix = "
    \n
      " - tree_suffix = "
    " - # first level of json is general overview - # FIXME: make sure json output first level always has only 1 level - tree_body = generate_tree_recursive(json_graph['children'][0]) - return tree_prefix + tree_body + tree_suffix - - -def generate_ipython(json_input): - from IPython.core.display import HTML - - html_output = generate_html(json_input, False) - - return HTML(""" - ${CSS} - ${LIBRARIES} -
    - ${CHART_SCRIPT} - """.replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', html_output['chart_script']).replace('${LIBRARIES}', html_output['libraries'])) - - -def generate_style_html(graph_json, include_meta_info): - treeflex_css = "\n" - css = "\n" - return { - 'treeflex_css': treeflex_css, - 'duckdb_css': css, - 'libraries': '', - 'chart_script': '' - } - -def gather_timing_information(json): - # add up all of the times - # measure each time as a percentage of the total time. - # then you can return a list of [phase, time, percentage] - top_level_timings = get_top_level_timings(json) - child_timings = get_child_timings(json['children'][0]) - -def generate(input_file, output_file): - with open_utf8(input_file, 'r') as f: - text = f.read() - - html_output = generate_style_html(text, True) - timing_table = generate_timing_html(text) - tree_output = generate_tree_html(text) - - # finally create and write the html - with open_utf8(output_file, "w+") as f: - html = """ - - - - - Query Profile Graph for Query - ${TREEFLEX_CSS} - - - -
    -
    - ${TIMING_TABLE} -
    - ${TREE} - - -""" - html = html.replace("${TREEFLEX_CSS}", html_output['treeflex_css']) - html = html.replace("${DUCKDB_CSS}", html_output['duckdb_css']) - html = html.replace("${TIMING_TABLE}", timing_table) - html = html.replace('${TREE}', tree_output) - f.write(html) - - -__all__ = ["generate"] \ No newline at end of file diff --git a/tools/pythonpkg/duckdb/query_graph/__main__.py b/tools/pythonpkg/duckdb/query_graph/__main__.py new file mode 100644 index 000000000000..1873a8374a7d --- /dev/null +++ b/tools/pythonpkg/duckdb/query_graph/__main__.py @@ -0,0 +1,323 @@ +import json +import os +import sys +import webbrowser +from functools import reduce +import argparse + +qgraph_css = """ +.styled-table { + border-collapse: collapse; + margin: 25px 0; + font-size: 0.9em; + font-family: sans-serif; + min-width: 400px; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); +} +.styled-table thead tr { + background-color: #009879; + color: #ffffff; + text-align: left; +} +.styled-table th, +.styled-table td { + padding: 12px 15px; +} +.styled-table tbody tr { + border-bottom: 1px solid #dddddd; +} + +.styled-table tbody tr:nth-of-type(even) { + background-color: #f3f3f3; +} + +.styled-table tbody tr:last-of-type { + border-bottom: 2px solid #009879; +} + +.node-body { + font-size:15px; +} +.tf-nc { + position: relative; + width: 250px; + text-align: center; + background-color: #fff100; +} +""" + +class NodeTiming: + + def __init__(self, phase, time): + self.phase = phase + self.time = time + # percentage is determined later. + self.percentage = 0 + + def calculate_percentage(self, total_time): + self.percentage = self.time/total_time + + def combine_timing(l, r): + # TODO: can only add timings for same-phase nodes + total_time = l.time + r.time + return NodeTiming(l.phase, total_time) + +class AllTimings: + + def __init__(self): + self.phase_to_timings = {} + + def add_node_timing(self, node_timing): + if node_timing.phase in self.phase_to_timings: + self.phase_to_timings[node_timing.phase].append(node_timing) + return + self.phase_to_timings[node_timing.phase] = [node_timing] + + def get_phase_timings(self, phase): + return self.phase_to_timings[phase] + + def get_summary_phase_timings(self, phase): + return reduce(NodeTiming.combine_timing, self.phase_to_timings[phase]) + + def get_phases(self): + phases = list(self.phase_to_timings.keys()) + phases.sort(key=lambda x: (self.get_summary_phase_timings(x)).time) + phases.reverse() + return phases + + def get_sum_of_all_timings(self): + total_timing_sum = 0 + for phase in self.phase_to_timings.keys(): + total_timing_sum += self.get_summary_phase_timings(phase).time + return total_timing_sum + + +def open_utf8(fpath, flags): + import sys + if sys.version_info[0] < 3: + return open(fpath, flags) + else: + return open(fpath, flags, encoding="utf8") + + +# if detailed profiling is enabled, then information on the timing of each stage of the query is available +# that is "top level timing information" +def get_top_level_timings(json): + return [] + +def get_child_timings(top_node, query_timings): + node_timing = NodeTiming(top_node['name'], float(top_node['timing'])) + query_timings.add_node_timing(node_timing) + for child in top_node['children']: + get_child_timings(child, query_timings) + +color_map = { + "HASH_JOIN": "#ffffba", + "PROJECTION": "#ffb3ba", + "SEQ_SCAN": "#baffc9", + "UNGROUPED_AGGREGATE": "#ffdfba", + "FILTER": "#bae1ff", + "ORDER_BY": "#facd60", + "PERFECT_HASH_GROUP_BY": "#ffffba", + "HASH_GROUP_BY": "#ffffba", + "NESTED_LOOP_JOIN": "#ffffba", + "STREAMING_LIMIT": "#facd60", + "COLUMN_DATA_SCAN": "#1ac0c6", + "TOP_N": "#ffdfba" +} + +def get_node_body(name, result, cardinality, extra_info, timing): + node_style = "" + stripped_name = name.strip() + if stripped_name in color_map: + node_style = f"background-color: {color_map[stripped_name]};" + + body = f"" + body += "
    " + new_name = name.replace("_", " ") + body += f"

    {new_name} ({result}s)

    " + if extra_info: + extra_info = extra_info.replace("[INFOSEPARATOR]", "----") + extra_info = extra_info.replace("

    ", "
    ") + body += f"

    {extra_info}

    " + body += f"

    cardinality = {cardinality}

    " + # TODO: Expand on timing. Usually available from a detailed profiling + body += "
    " + body += "
    " + return body + + +def generate_tree_recursive(json_graph): + node_prefix_html = "
  • " + node_suffix_html = "
  • " + node_body = get_node_body(json_graph["name"], + json_graph["timing"], + json_graph["cardinality"], + json_graph["extra_info"].replace("\n", "
    "), + json_graph["timings"]) + + children_html = "" + if len(json_graph['children']) >= 1: + children_html += "
      " + for child in json_graph["children"]: + children_html += generate_tree_recursive(child) + children_html += "
    " + return node_prefix_html + node_body + children_html + node_suffix_html + +# For generating the table in the top left. +def generate_timing_html(graph_json, query_timings): + json_graph = json.loads(graph_json) + gather_timing_information(json_graph, query_timings) + total_time = float(json_graph['timing']) + table_head = """ + + + + + + + + """ + + table_body = "" + table_end = "
    PhaseTimePercentage
    " + + execution_time = query_timings.get_sum_of_all_timings() + + all_phases = query_timings.get_phases() + query_timings.add_node_timing(NodeTiming("TOTAL TIME", total_time)) + query_timings.add_node_timing(NodeTiming("Execution Time", execution_time)) + all_phases = ["TOTAL TIME", "Execution Time"] + all_phases + for phase in all_phases: + summarized_phase = query_timings.get_summary_phase_timings(phase) + summarized_phase.calculate_percentage(total_time) + phase_column = f"{phase}" if phase == "TOTAL TIME" or phase == "Execution Time" else phase + table_body += f""" + + {phase_column} + {summarized_phase.time} + {str(summarized_phase.percentage * 100)[:6]}% + +""" + table_body += table_end + return table_head + table_body + +def generate_tree_html(graph_json): + json_graph = json.loads(graph_json) + tree_prefix = "
    \n
      " + tree_suffix = "
    " + # first level of json is general overview + # FIXME: make sure json output first level always has only 1 level + tree_body = generate_tree_recursive(json_graph['children'][0]) + return tree_prefix + tree_body + tree_suffix + + +def generate_ipython(json_input): + from IPython.core.display import HTML + + html_output = generate_html(json_input, False) + + return HTML(""" + ${CSS} + ${LIBRARIES} +
    + ${CHART_SCRIPT} + """.replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', html_output['chart_script']).replace('${LIBRARIES}', html_output['libraries'])) + + +def generate_style_html(graph_json, include_meta_info): + treeflex_css = "\n" + css = "\n" + return { + 'treeflex_css': treeflex_css, + 'duckdb_css': css, + 'libraries': '', + 'chart_script': '' + } + +# ???? +def gather_timing_information(json, query_timings): + # add up all of the times + # measure each time as a percentage of the total time. + # then you can return a list of [phase, time, percentage] + top_level_timings = get_top_level_timings(json) + child_timings = get_child_timings(json['children'][0], query_timings) + +def translate_json_to_html(input_file, output_file): + query_timings = AllTimings() + with open_utf8(input_file, 'r') as f: + text = f.read() + + html_output = generate_style_html(text, True) + timing_table = generate_timing_html(text, query_timings) + tree_output = generate_tree_html(text) + + # finally create and write the html + with open_utf8(output_file, "w+") as f: + html = """ + + + + + Query Profile Graph for Query + ${TREEFLEX_CSS} + + + +
    +
    + ${TIMING_TABLE} +
    + ${TREE} + + +""" + html = html.replace("${TREEFLEX_CSS}", html_output['treeflex_css']) + html = html.replace("${DUCKDB_CSS}", html_output['duckdb_css']) + html = html.replace("${TIMING_TABLE}", timing_table) + html = html.replace('${TREE}', tree_output) + f.write(html) + +def main(): + sys.path.insert(0, 'benchmark') + + parser = argparse.ArgumentParser( + prog='Query Graph Generator', + description='Given a json profile output, generates an html file that shows the query graph') + parser.add_argument('profile_input', help='profile input in json') + parser.add_argument('--out', required=False, default=False) + parser.add_argument('--open', required=False, action='store_true', default=True) + args = parser.parse_args() + + input = args.profile_input + output = args.out + if not args.out: + if ".json" in input: + output = input.replace(".json", ".html") + else: + print("please provide profile output in json") + exit(1) + else: + if ".html" in args.out: + output = args.out + else: + print("please provide valid .html file for output name") + exit(1) + + open_output = args.open + + translate_json_to_html(input, output) + + with open(output, 'r') as f: + text = f.read() + + if open_output: + webbrowser.open('file:///Users/tomebergen/scripts/run_imdb/tpch_new_html/q08.html', new=2) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/tools/pythonpkg/setup.py b/tools/pythonpkg/setup.py index b925bab18917..ffccec8409af 100644 --- a/tools/pythonpkg/setup.py +++ b/tools/pythonpkg/setup.py @@ -319,7 +319,7 @@ def setup_data_files(data_files): packages = [ lib_name, 'duckdb.typing', - 'duckdb.duckdb_query_graph', + 'duckdb.query_graph', 'duckdb.functional', 'duckdb.value', 'duckdb-stubs', From 40255e931915d4ccfed9a71b19aa7c98561eae74 Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Fri, 6 Oct 2023 12:54:30 +0200 Subject: [PATCH 16/26] remove hardcoded string, remove support for detailed profiling, remove unused code, remove support for python2 --- scripts/generate_querygraph.py | 45 ------------------- .../pythonpkg/duckdb/query_graph/__main__.py | 18 ++------ 2 files changed, 4 insertions(+), 59 deletions(-) delete mode 100644 scripts/generate_querygraph.py diff --git a/scripts/generate_querygraph.py b/scripts/generate_querygraph.py deleted file mode 100644 index 0b19b233bf6c..000000000000 --- a/scripts/generate_querygraph.py +++ /dev/null @@ -1,45 +0,0 @@ -# generate_querygraph.py -# This script takes a json file as input that is the result of the QueryProfiler of duckdb -# and converts it into a Query Graph. - -import os -import sys -from functools import reduce -from python_helpers import open_utf8 - -from duckdb.query_graph import generate - -sys.path.insert(0, 'benchmark') - -arguments = sys.argv -if len(arguments) <= 1: - print("Usage: python generate_querygraph.py [input.json] [output.html] [open={1,0}]") - exit(1) - -input = arguments[1] -if len(arguments) <= 2: - if ".json" in input: - output = input.replace(".json", ".html") - else: - output = input + ".html" -else: - output = arguments[2] - -open_output = True -if len(arguments) >= 4: - open_arg = arguments[3].lower().replace('open=', '') - if open_arg == "1" or open_arg == "true": - open_output = True - elif open_arg == "0" or open_arg == "false": - open_output = False - else: - print("Incorrect input for open_output, expected TRUE or FALSE") - exit(1) - -generate(input, output) - -with open(output, 'r') as f: - text = f.read() - -if open_output: - os.system('open "' + output.replace('"', '\\"') + '"') diff --git a/tools/pythonpkg/duckdb/query_graph/__main__.py b/tools/pythonpkg/duckdb/query_graph/__main__.py index 1873a8374a7d..1edbc30313f6 100644 --- a/tools/pythonpkg/duckdb/query_graph/__main__.py +++ b/tools/pythonpkg/duckdb/query_graph/__main__.py @@ -95,16 +95,11 @@ def get_sum_of_all_timings(self): def open_utf8(fpath, flags): import sys if sys.version_info[0] < 3: - return open(fpath, flags) + print("Please use python3") + exit(1) else: return open(fpath, flags, encoding="utf8") - -# if detailed profiling is enabled, then information on the timing of each stage of the query is available -# that is "top level timing information" -def get_top_level_timings(json): - return [] - def get_child_timings(top_node, query_timings): node_timing = NodeTiming(top_node['name'], float(top_node['timing'])) query_timings.add_node_timing(node_timing) @@ -237,12 +232,10 @@ def generate_style_html(graph_json, include_meta_info): 'chart_script': '' } -# ???? def gather_timing_information(json, query_timings): # add up all of the times # measure each time as a percentage of the total time. # then you can return a list of [phase, time, percentage] - top_level_timings = get_top_level_timings(json) child_timings = get_child_timings(json['children'][0], query_timings) def translate_json_to_html(input_file, output_file): @@ -287,7 +280,7 @@ def main(): parser = argparse.ArgumentParser( prog='Query Graph Generator', - description='Given a json profile output, generates an html file that shows the query graph') + description='Given a json profile output, generate a html file showing the query graph and timings of operators') parser.add_argument('profile_input', help='profile input in json') parser.add_argument('--out', required=False, default=False) parser.add_argument('--open', required=False, action='store_true', default=True) @@ -312,11 +305,8 @@ def main(): translate_json_to_html(input, output) - with open(output, 'r') as f: - text = f.read() - if open_output: - webbrowser.open('file:///Users/tomebergen/scripts/run_imdb/tpch_new_html/q08.html', new=2) + webbrowser.open('file://' + os.path.abspath(output), new=2) if __name__ == '__main__': From 1ac182c68462a040add5bbbd461f8cae165069c9 Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Mon, 9 Oct 2023 10:16:35 +0200 Subject: [PATCH 17/26] PR comments still cleaning up old generat_query_graph.py script --- tools/pythonpkg/duckdb/query_graph/__main__.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tools/pythonpkg/duckdb/query_graph/__main__.py b/tools/pythonpkg/duckdb/query_graph/__main__.py index 1edbc30313f6..b0f6be687d8d 100644 --- a/tools/pythonpkg/duckdb/query_graph/__main__.py +++ b/tools/pythonpkg/duckdb/query_graph/__main__.py @@ -93,12 +93,7 @@ def get_sum_of_all_timings(self): def open_utf8(fpath, flags): - import sys - if sys.version_info[0] < 3: - print("Please use python3") - exit(1) - else: - return open(fpath, flags, encoding="utf8") + return open(fpath, flags, encoding="utf8") def get_child_timings(top_node, query_timings): node_timing = NodeTiming(top_node['name'], float(top_node['timing'])) @@ -236,7 +231,7 @@ def gather_timing_information(json, query_timings): # add up all of the times # measure each time as a percentage of the total time. # then you can return a list of [phase, time, percentage] - child_timings = get_child_timings(json['children'][0], query_timings) + get_child_timings(json['children'][0], query_timings) def translate_json_to_html(input_file, output_file): query_timings = AllTimings() @@ -276,8 +271,9 @@ def translate_json_to_html(input_file, output_file): f.write(html) def main(): - sys.path.insert(0, 'benchmark') - + if sys.version_info[0] < 3: + print("Please use python3") + exit(1) parser = argparse.ArgumentParser( prog='Query Graph Generator', description='Given a json profile output, generate a html file showing the query graph and timings of operators') From 07077611e04bac8b368d4f4180c40c9ca70a6c2a Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Tue, 10 Oct 2023 10:11:05 +0200 Subject: [PATCH 18/26] run make format fix a regenerate_python_stubs --- tools/pythonpkg/duckdb-stubs/__init__.pyi | 712 +++--------------- .../duckdb-stubs/functional/__init__.pyi | 33 - .../duckdb-stubs/typing/__init__.pyi | 36 - .../pythonpkg/duckdb-stubs/value/__init__.pyi | 0 .../duckdb-stubs/value/constant/__init__.pyi | 116 --- 5 files changed, 96 insertions(+), 801 deletions(-) delete mode 100644 tools/pythonpkg/duckdb-stubs/functional/__init__.pyi delete mode 100644 tools/pythonpkg/duckdb-stubs/typing/__init__.pyi delete mode 100644 tools/pythonpkg/duckdb-stubs/value/__init__.pyi delete mode 100644 tools/pythonpkg/duckdb-stubs/value/constant/__init__.pyi diff --git a/tools/pythonpkg/duckdb-stubs/__init__.pyi b/tools/pythonpkg/duckdb-stubs/__init__.pyi index 79d9f821629b..17b962f5def8 100644 --- a/tools/pythonpkg/duckdb-stubs/__init__.pyi +++ b/tools/pythonpkg/duckdb-stubs/__init__.pyi @@ -3,56 +3,10 @@ # generated. These should be annotated with a comment like # # stubgen override # to help the sanity of maintainers. +from typing import Any, ClassVar -import duckdb.typing as typing -import duckdb.functional as functional -from duckdb.typing import DuckDBPyType -from duckdb.functional import FunctionNullHandling, PythonUDFType -from duckdb.value.constant import ( - Value, - NullValue, - BooleanValue, - UnsignedBinaryValue, - UnsignedShortValue, - UnsignedIntegerValue, - UnsignedLongValue, - BinaryValue, - ShortValue, - IntegerValue, - LongValue, - HugeIntegerValue, - FloatValue, - DoubleValue, - DecimalValue, - StringValue, - UUIDValue, - BitValue, - BlobValue, - DateValue, - IntervalValue, - TimestampValue, - TimestampSecondValue, - TimestampMilisecondValue, - TimestampNanosecondValue, - TimestampTimeZoneValue, - TimeValue, - TimeTimeZoneValue, -) - -# We also run this in python3.7, where this is needed -from typing_extensions import Literal -# stubgen override - missing import of Set -from typing import Any, ClassVar, Set, Optional, Callable -from io import StringIO, TextIOBase - -from typing import overload, Dict, List, Union -import pandas -# stubgen override - unfortunately we need this for version checks -import sys -import fsspec -import pyarrow.lib -import polars -# stubgen override - This should probably not be exposed +from typing import overload +_clean_default_connection: PyCapsule apilevel: str comment: token_type default_connection: DuckDBPyConnection @@ -63,488 +17,139 @@ operator: token_type paramstyle: str string_const: token_type threadsafety: int -__standard_vector_size__: int -STANDARD: ExplainType -ANALYZE: ExplainType -DEFAULT: PythonExceptionHandling -RETURN_NULL: PythonExceptionHandling -ROWS: RenderMode -COLUMNS: RenderMode - -__version__: str - -__interactive__: bool -__jupyter__: bool - -class BinderException(ProgrammingError): ... - -class CastException(DataError): ... - -class CatalogException(ProgrammingError): ... - -class ConnectionException(OperationalError): ... - -class ConstraintException(IntegrityError): ... - -class ConversionException(DataError): ... - -class DataError(Error): ... - -class ExplainType: - STANDARD: ExplainType - ANALYZE: ExplainType - def __int__(self) -> int: ... - def __index__(self) -> int: ... - @property - def __members__(self) -> Dict[str, ExplainType]: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -class RenderMode: - ROWS: RenderMode - COLUMNS: RenderMode - def __int__(self) -> int: ... - def __index__(self) -> int: ... - @property - def __members__(self) -> Dict[str, RenderMode]: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -class PythonExceptionHandling: - DEFAULT: PythonExceptionHandling - RETURN_NULL: PythonExceptionHandling - def __int__(self) -> int: ... - def __index__(self) -> int: ... - @property - def __members__(self) -> Dict[str, PythonExceptionHandling]: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -class Expression: - def __init__(self, *args, **kwargs) -> None: ... - def __neg__(self) -> "Expression": ... - - def __add__(self, expr: "Expression") -> "Expression": ... - def __radd__(self, expr: "Expression") -> "Expression": ... - - def __sub__(self, expr: "Expression") -> "Expression": ... - def __rsub__(self, expr: "Expression") -> "Expression": ... - - def __mul__(self, expr: "Expression") -> "Expression": ... - def __rmul__(self, expr: "Expression") -> "Expression": ... - - def __div__(self, expr: "Expression") -> "Expression": ... - def __rdiv__(self, expr: "Expression") -> "Expression": ... - - def __truediv__(self, expr: "Expression") -> "Expression": ... - def __rtruediv__(self, expr: "Expression") -> "Expression": ... - - def __floordiv__(self, expr: "Expression") -> "Expression": ... - def __rfloordiv__(self, expr: "Expression") -> "Expression": ... - - def __mod__(self, expr: "Expression") -> "Expression": ... - def __rmod__(self, expr: "Expression") -> "Expression": ... - - def __pow__(self, expr: "Expression") -> "Expression": ... - def __rpow__(self, expr: "Expression") -> "Expression": ... - - def __and__(self, expr: "Expression") -> "Expression": ... - def __rand__(self, expr: "Expression") -> "Expression": ... - def __or__(self, expr: "Expression") -> "Expression": ... - def __ror__(self, expr: "Expression") -> "Expression": ... - def __invert__(self) -> "Expression": ... - - def __eq__(# type: ignore[override] - self, expr: "Expression") -> "Expression": ... - def __ne__(# type: ignore[override] - self, expr: "Expression") -> "Expression": ... - def __gt__(self, expr: "Expression") -> "Expression": ... - def __ge__(self, expr: "Expression") -> "Expression": ... - def __lt__(self, expr: "Expression") -> "Expression": ... - def __le__(self, expr: "Expression") -> "Expression": ... - - def show(self, max_width: Optional[int] = None, max_rows: Optional[int] = None, max_col_width: Optional[int] = None, null_value: Optional[str] = None, render_mode: Optional[RenderMode] = None) -> None: ... - def __repr__(self) -> str: ... - def alias(self, alias: str) -> None: ... - def when(self, condition: "Expression", value: "Expression") -> "Expression": ... - def otherwise(self, value: "Expression") -> "Expression": ... - def cast(self, type: DuckDBPyType) -> "Expression": ... - def asc(self) -> "Expression": ... - def desc(self) -> "Expression": ... - def nulls_first(self) -> "Expression": ... - def nulls_last(self) -> "Expression": ... - def isin(self, *cols: "Expression") -> "Expression": ... - def isnotin(self, *cols: "Expression") -> "Expression": ... - -def StarExpression(exclude: Optional[List[str]]) -> Expression: ... -def ColumnExpression(column: str) -> Expression: ... -def ConstantExpression(val: Any) -> Expression: ... -def CaseExpression(condition: Expression, value: Expression) -> Expression: ... -def FunctionExpression(function: str, *cols: Expression) -> Expression: ... class DuckDBPyConnection: def __init__(self, *args, **kwargs) -> None: ... - def append(self, table_name: str, df: pandas.DataFrame) -> DuckDBPyConnection: ... - def arrow(self, rows_per_batch: int = ...) -> pyarrow.lib.Table: ... + def append(self, table_name: str, df: object) -> DuckDBPyConnection: ... + def arrow(self, chunk_size: int = ...) -> object: ... def begin(self) -> DuckDBPyConnection: ... def close(self) -> None: ... def commit(self) -> DuckDBPyConnection: ... def cursor(self) -> DuckDBPyConnection: ... - def df(self) -> pandas.DataFrame: ... + @overload + def df(self) -> object: ... + @overload + def df(self, df: object) -> DuckDBPyRelation: ... + @overload + def df(aliasoffrom_df) -> Any: ... def duplicate(self) -> DuckDBPyConnection: ... def execute(self, query: str, parameters: object = ..., multiple_parameter_sets: bool = ...) -> DuckDBPyConnection: ... def executemany(self, query: str, parameters: object = ...) -> DuckDBPyConnection: ... - def fetch_arrow_table(self, rows_per_batch: int = ...) -> pyarrow.lib.Table: ... - def fetch_df(self, *args, **kwargs) -> pandas.DataFrame: ... - def fetch_df_chunk(self, *args, **kwargs) -> pandas.DataFrame: ... - def fetch_record_batch(self, rows_per_batch: int = ...) -> pyarrow.lib.RecordBatchReader: ... - def fetchall(self) -> List[Any]: ... - def fetchdf(self, *args, **kwargs) -> pandas.DataFrame: ... - def fetchmany(self, size: int = ...) -> List[Any]: ... + def fetch_arrow_table(self, chunk_size: int = ...) -> object: ... + def fetch_df(self) -> object: ... + def fetch_df_chunk(self, vectors_per_chunk: int = ...) -> object: ... + def fetch_record_batch(self, chunk_size: int = ...) -> object: ... + def fetchall(self) -> list: ... + def fetchdf(self) -> object: ... def fetchnumpy(self) -> dict: ... - def fetchone(self) -> Optional[tuple]: ... - def from_arrow(self, arrow_object: object) -> DuckDBPyRelation: ... - def read_json( - self, - file_name: str, - columns: Optional[Dict[str,str]] = None, - sample_size: Optional[int] = None, - maximum_depth: Optional[int] = None, - records: Optional[str] = None, - format: Optional[str] = None - ) -> DuckDBPyRelation: ... - def read_csv( - self, - path_or_buffer: Union[str, StringIO, TextIOBase], - header: Optional[bool | int] = None, - compression: Optional[str] = None, - sep: Optional[str] = None, - delimiter: Optional[str] = None, - dtype: Optional[Dict[str, str] | List[str]] = None, - na_values: Optional[str] = None, - skiprows: Optional[int] = None, - quotechar: Optional[str] = None, - escapechar: Optional[str] = None, - encoding: Optional[str] = None, - parallel: Optional[bool] = None, - date_format: Optional[str] = None, - timestamp_format: Optional[str] = None, - sample_size: Optional[int] = None, - all_varchar: Optional[bool] = None, - normalize_names: Optional[bool] = None, - filename: Optional[bool] = None, - null_padding: Optional[bool] = None, - names: Optional[List[str]] = None - ) -> DuckDBPyRelation: ... - def from_csv_auto( - self, - path_or_buffer: Union[str, StringIO, TextIOBase], - header: Optional[bool | int] = None, - compression: Optional[str] = None, - sep: Optional[str] = None, - delimiter: Optional[str] = None, - dtype: Optional[Dict[str, str] | List[str]] = None, - na_values: Optional[str] = None, - skiprows: Optional[int] = None, - quotechar: Optional[str] = None, - escapechar: Optional[str] = None, - encoding: Optional[str] = None, - parallel: Optional[bool] = None, - date_format: Optional[str] = None, - timestamp_format: Optional[str] = None, - sample_size: Optional[int] = None, - all_varchar: Optional[bool] = None, - normalize_names: Optional[bool] = None, - filename: Optional[bool] = None, - null_padding: Optional[bool] = None, - names: Optional[List[str]] = None - ) -> DuckDBPyRelation: ... - def from_df(self, df: pandas.DataFrame = ...) -> DuckDBPyRelation: ... - @overload - def read_parquet(self, file_glob: str, binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ...) -> DuckDBPyRelation: ... - @overload - def read_parquet(self, file_globs: List[str], binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ...) -> DuckDBPyRelation: ... - @overload - def from_parquet(self, file_glob: str, binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ...) -> DuckDBPyRelation: ... - @overload - def from_parquet(self, file_globs: List[str], binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ...) -> DuckDBPyRelation: ... + def fetchone(self) -> object: ... + def from_arrow(self, arrow_object: object, rows_per_thread: int = ...) -> DuckDBPyRelation: ... + def from_csv_auto(self, file_name: str) -> DuckDBPyRelation: ... + def from_df(self, df: object = ...) -> DuckDBPyRelation: ... + def from_parquet(self, file_name: str, binary_as_string: bool = ...) -> DuckDBPyRelation: ... + def from_query(self, query: str, alias: str = ...) -> DuckDBPyRelation: ... def from_substrait(self, proto: bytes) -> DuckDBPyRelation: ... def get_substrait(self, query: str) -> DuckDBPyRelation: ... - def get_substrait_json(self, query: str) -> DuckDBPyRelation: ... - def from_substrait_json(self, json: str) -> DuckDBPyRelation: ... def get_table_names(self, query: str) -> Set[str]: ... - def install_extension(self, *args, **kwargs) -> None: ... - def interrupt(self) -> None: ... - def list_filesystems(self) -> List[Any]: ... - def filesystem_is_registered(self, name: str) -> bool: ... - def load_extension(self, extension: str) -> None: ... - def pl(self, rows_per_batch: int = ..., connection: DuckDBPyConnection = ...) -> polars.DataFrame: ... - def torch(self, connection: DuckDBPyConnection = ...) -> dict: ... - def tf(self, connection: DuckDBPyConnection = ...) -> dict: ... - - def from_query(self, query: str, **kwargs) -> DuckDBPyRelation: ... - def query(self, query: str, **kwargs) -> DuckDBPyRelation: ... - def sql(self, query: str, **kwargs) -> DuckDBPyRelation: ... - - def register(self, view_name: str, python_object: object) -> DuckDBPyConnection: ... - def remove_function(self, name: str) -> DuckDBPyConnection: ... - def create_function( - self, - name: str, - func: Callable, - args: Optional[List[DuckDBPyType]] = None, - return_type: Optional[DuckDBPyType] = None, - vectorized: Optional[bool] = False, - null_handling: Optional[FunctionNullHandling] = FunctionNullHandling.DEFAULT, - exception_handling: Optional[PythonExceptionHandling] = PythonExceptionHandling.DEFAULT, - side_effects: Optional[bool] = False) -> DuckDBPyConnection: ... - def register_filesystem(self, filesystem: fsspec.AbstractFileSystem) -> None: ... + def query(self, query: str, alias: str = ...) -> DuckDBPyRelation: ... + def register(self, view_name: str, python_object: object, rows_per_thread: int = ...) -> DuckDBPyConnection: ... def rollback(self) -> DuckDBPyConnection: ... def table(self, table_name: str) -> DuckDBPyRelation: ... def table_function(self, name: str, parameters: object = ...) -> DuckDBPyRelation: ... def unregister(self, view_name: str) -> DuckDBPyConnection: ... - def unregister_filesystem(self, name: str) -> None: ... def values(self, values: object) -> DuckDBPyRelation: ... def view(self, view_name: str) -> DuckDBPyRelation: ... - def sqltype(self, type_str: str) -> DuckDBPyType: ... - def dtype(self, type_str: str) -> DuckDBPyType: ... - def type(self, type_str: str) -> DuckDBPyType: ... - def struct_type(self, fields: Union[Dict[str, DuckDBPyType], List[str]]) -> DuckDBPyType: ... - def row_type(self, fields: Union[Dict[str, DuckDBPyType], List[str]]) -> DuckDBPyType: ... - def union_type(self, members: Union[Dict[str, DuckDBPyType], List[str]]) -> DuckDBPyType: ... - def string_type(self, collation: str = "") -> DuckDBPyType: ... - def enum_type(self, name: str, type: DuckDBPyType, values: List[Any]) -> DuckDBPyType: ... - def decimal_type(self, width: int, scale: int) -> DuckDBPyType: ... - def array_type(self, type: DuckDBPyType) -> DuckDBPyType: ... - def list_type(self, type: DuckDBPyType) -> DuckDBPyType: ... - def map_type(self, key: DuckDBPyType, value: DuckDBPyType) -> DuckDBPyType: ... - def __enter__(self) -> DuckDBPyConnection: ... - def __exit__(self, exc_type: object, exc: object, traceback: object) -> None: ... - @property - def description(self) -> Optional[List[Any]]: ... + def __enter__(self, database: str = ..., read_only: bool = ..., config: dict = ..., check_same_thread: bool = ...) -> DuckDBPyConnection: ... + def __exit__(self, exc_type: object, exc: object, traceback: object) -> bool: ... @property - def rowcount(self) -> int: ... + def description(self) -> object: ... class DuckDBPyRelation: - def close(self) -> None: ... - def __getattr__(self, name: str) -> DuckDBPyRelation: ... - def __getitem__(self, name: str) -> DuckDBPyRelation: ... def __init__(self, *args, **kwargs) -> None: ... - def __contains__(self, name: str) -> bool: ... + def abs(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... def aggregate(self, aggr_expr: str, group_expr: str = ...) -> DuckDBPyRelation: ... - def any_value(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... def apply(self, function_name: str, function_aggr: str, group_expr: str = ..., function_parameter: str = ..., projected_columns: str = ...) -> DuckDBPyRelation: ... - def arg_max(self, arg_column: str, value_column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def arg_min(self, arg_column: str, value_column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def arrow(self, batch_size: int = ...) -> pyarrow.lib.Table: ... - def avg(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def bit_and(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def bit_or(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def bit_xor(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def bitstring_agg(self, column: str, min: Optional[int], max: Optional[int], groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def bool_and(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def bool_or(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def count(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def arrow(self, batch_size: int = ...) -> object: ... + def count(self, count_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... def create(self, table_name: str) -> None: ... def create_view(self, view_name: str, replace: bool = ...) -> DuckDBPyRelation: ... - def cume_dist(self, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def dense_rank(self, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def cummax(self, aggregation_columns: str) -> DuckDBPyRelation: ... + def cummin(self, aggregation_columns: str) -> DuckDBPyRelation: ... + def cumprod(self, aggregation_columns: str) -> DuckDBPyRelation: ... + def cumsum(self, aggregation_columns: str) -> DuckDBPyRelation: ... def describe(self) -> DuckDBPyRelation: ... - def df(self, *args, **kwargs) -> pandas.DataFrame: ... + def df(self) -> object: ... def distinct(self) -> DuckDBPyRelation: ... def except_(self, other_rel: DuckDBPyRelation) -> DuckDBPyRelation: ... - def execute(self, *args, **kwargs) -> DuckDBPyRelation: ... - def explain(self, type: Optional[Literal['standard', 'analyze'] | int] = 'standard') -> str: ... - def favg(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def fetchall(self) -> List[Any]: ... - def fetchmany(self, size: int = ...) -> List[Any]: ... - def fetchnumpy(self) -> dict: ... - def fetchone(self) -> Optional[tuple]: ... - def fetchdf(self, *args, **kwargs) -> Any: ... - def fetch_arrow_reader(self, batch_size: int = ...) -> pyarrow.lib.RecordBatchReader: ... - def fetch_arrow_table(self, rows_per_batch: int = ...) -> pyarrow.lib.Table: ... - def filter(self, filter_expr: Union[Expression, str]) -> DuckDBPyRelation: ... - def first(self, column: str, window_spec: str, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... - def first_value(self, column: str, window_spec: str, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... - def fsum(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def geomean(self, column: str, groups: str, projected_columns: str) -> DuckDBPyRelation: ... - def histogram(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def execute(self, *args, **kwargs) -> Any: ... + def fetchall(self) -> object: ... + def fetchone(self) -> object: ... + def filter(self, filter_expr: str) -> DuckDBPyRelation: ... def insert(self, values: object) -> None: ... def insert_into(self, table_name: str) -> None: ... def intersect(self, other_rel: DuckDBPyRelation) -> DuckDBPyRelation: ... def join(self, other_rel: DuckDBPyRelation, condition: str, how: str = ...) -> DuckDBPyRelation: ... - def lag(self, column: str, window_spec: str, offset: int, default_value: str, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... - def last(self, column: str, window_spec: str, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... - def last_value(self, column: str, window_spec: str, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... - def lead(self, column: str, window_spec: str, offset: int, default_value: str, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... + def kurt(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... def limit(self, n: int, offset: int = ...) -> DuckDBPyRelation: ... - def list(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def map(self, map_function: function, schema: Optional[Dict[str, DuckDBPyType]]) -> DuckDBPyRelation: ... + def mad(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... + def map(self, map_function: function) -> DuckDBPyRelation: ... def max(self, max_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... def mean(self, mean_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... def median(self, median_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... def min(self, min_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... def mode(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... - def n_tile(self, window_spec: str, num_buckets: int, projected_columns: str) -> DuckDBPyRelation: ... - def nth_value(self, column: str, window_spec: str, offset: int, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... def order(self, order_expr: str) -> DuckDBPyRelation: ... - def sort(self, *cols: Expression) -> DuckDBPyRelation: ... - def project(self, *cols: Union[str, Expression]) -> DuckDBPyRelation: ... - def select(self, *cols: Union[str, Expression]) -> DuckDBPyRelation: ... - def percent_rank(self, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def pl(self, rows_per_batch: int = ..., connection: DuckDBPyConnection = ...) -> polars.DataFrame: ... - def product(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def prod(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... + def project(self, project_expr: str) -> DuckDBPyRelation: ... def quantile(self, q: str, quantile_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... - def quantile_cont(self, column: str, q: Union[float, List[float]], groups: str, projected_columns: str) -> DuckDBPyRelation: ... - def quantile_disc(self, column: str, q: Union[float, List[float]], groups: str, projected_columns: str) -> DuckDBPyRelation: ... - def query(self, virtual_table_name: str, sql_query: str) -> DuckDBPyRelation: ... - def rank(self, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def rank_dense(self, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def record_batch(self, batch_size: int = ...) -> pyarrow.lib.RecordBatchReader: ... - def row_number(self, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def select_types(self, types: List[Union[str, DuckDBPyType]]) -> DuckDBPyRelation: ... - def select_dtypes(self, types: List[Union[str, DuckDBPyType]]) -> DuckDBPyRelation: ... + def query(self, *args, **kwargs) -> Any: ... + def record_batch(self, batch_size: int = ...) -> object: ... + def sem(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... def set_alias(self, alias: str) -> DuckDBPyRelation: ... - def show(self) -> None: ... - def sql_query(self) -> str: ... - def std(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def stddev(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def stddev_pop(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def stddev_samp(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def string_agg(self, column: str, sep: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def skew(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... + def std(self, std_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... def sum(self, sum_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... - def to_arrow_table(self, batch_size: int = ...) -> pyarrow.lib.Table: ... - def to_csv( - self, - file_name: str, - sep: Optional[str], - na_rep: Optional[str], - header: Optional[bool], - quotechar: Optional[str], - escapechar: Optional[str], - date_format: Optional[str], - timestamp_format: Optional[str], - quoting: Optional[str | int], - encoding: Optional[str], - compression: Optional[str] - ) -> None: ... - def to_df(self, *args, **kwargs) -> pandas.DataFrame: ... - def to_parquet( - self, - file_name: str, - compression: Optional[str] - ) -> None: ... - def to_table(self, table_name: str) -> None: ... - def to_view(self, view_name: str, replace: bool = ...) -> DuckDBPyRelation: ... - def torch(self, connection: DuckDBPyConnection = ...) -> dict: ... - def tf(self, connection: DuckDBPyConnection = ...) -> dict: ... + def to_arrow_table(self, batch_size: int = ...) -> object: ... + def to_df(self) -> object: ... + @overload def union(self, union_rel: DuckDBPyRelation) -> DuckDBPyRelation: ... + @overload + def union(self, arg0: DuckDBPyRelation) -> DuckDBPyRelation: ... def unique(self, unique_aggr: str) -> DuckDBPyRelation: ... - def var(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def var_pop(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def var_samp(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def variance(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... - def write_csv( - self, - file_name: str, - sep: Optional[str], - na_rep: Optional[str], - header: Optional[bool], - quotechar: Optional[str], - escapechar: Optional[str], - date_format: Optional[str], - timestamp_format: Optional[str], - quoting: Optional[str | int], - encoding: Optional[str], - compression: Optional[str] - ) -> None: ... - def write_parquet( - self, - file_name: str, - compression: Optional[str] - ) -> None: ... + def value_counts(self, value_counts_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... + def var(self, var_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... + def write_csv(self, file_name: str) -> None: ... def __len__(self) -> int: ... @property def alias(self) -> str: ... @property - def columns(self) -> List[Any]: ... + def columns(self) -> list: ... @property - def dtypes(self) -> List[DuckDBPyType]: ... - @property - def description(self) -> List[Any]: ... + def dtypes(self) -> list: ... @property def shape(self) -> tuple: ... @property def type(self) -> str: ... @property - def types(self) -> List[DuckDBPyType]: ... - -class Error(Exception): ... - -class FatalException(Error): ... - -class HTTPException(IOException): - status_code: int - body: str - reason: str - headers: Dict[str, str] - -class IOException(OperationalError): ... - -class IntegrityError(Error): ... - -class InternalError(Error): ... - -class InternalException(InternalError): ... - -class InterruptException(Error): ... - -class InvalidInputException(ProgrammingError): ... - -class InvalidTypeException(ProgrammingError): ... - -class NotImplementedException(NotSupportedError): ... + def types(self) -> list: ... -class NotSupportedError(Error): ... - -class OperationalError(Error): ... - -class OutOfMemoryException(OperationalError): ... - -class OutOfRangeException(DataError): ... - -class ParserException(ProgrammingError): ... - -class PermissionException(Error): ... - -class ProgrammingError(Error): ... - -class SequenceException(Error): ... - -class SerializationException(OperationalError): ... - -class StandardException(Error): ... - -class SyntaxException(ProgrammingError): ... - -class TransactionException(OperationalError): ... - -class TypeMismatchException(DataError): ... - -class ValueOutOfRangeException(DataError): ... - -class Warning(Exception): ... +class DuckDBPyResult: + def __init__(self, *args, **kwargs) -> None: ... + def arrow(self, chunk_size: int = ...) -> object: ... + def close(self) -> None: ... + def description(self) -> list: ... + def df(self) -> object: ... + def fetch_arrow_reader(self, approx_batch_size: int) -> object: ... + def fetch_arrow_table(self, chunk_size: int = ...) -> object: ... + def fetch_df(self) -> object: ... + def fetch_df_chunk(self, arg0: int) -> object: ... + def fetchall(self) -> list: ... + def fetchdf(self) -> object: ... + def fetchnumpy(self) -> dict: ... + def fetchone(self) -> object: ... class token_type: - # stubgen override - these make mypy sad - #__doc__: ClassVar[str] = ... # read-only - #__members__: ClassVar[dict] = ... # read-only + __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... comment: ClassVar[token_type] = ... identifier: ClassVar[token_type] = ... @@ -556,9 +161,7 @@ class token_type: def __eq__(self, other: object) -> bool: ... def __getstate__(self) -> int: ... def __hash__(self) -> int: ... - # stubgen override - pybind only puts index in python >= 3.8: https://github.com/EricCousineau-TRI/pybind11/blob/54430436/include/pybind11/pybind11.h#L1789 - if sys.version_info >= (3, 7): - def __index__(self) -> int: ... + def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... def __setstate__(self, state: int) -> None: ... @@ -566,152 +169,29 @@ class token_type: def name(self) -> str: ... @property def value(self) -> int: ... - @property - # stubgen override - this gets removed by stubgen but it shouldn't - def __members__(self) -> object: ... - -def aggregate(df: pandas.DataFrame, aggr_expr: str, group_expr: str = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def alias(df: pandas.DataFrame, alias: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def connect(database: str = ..., read_only: bool = ..., config: dict = ...) -> DuckDBPyConnection: ... -def distinct(df: pandas.DataFrame, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def filter(df: pandas.DataFrame, filter_expr: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def from_substrait_json(jsonm: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def limit(df: pandas.DataFrame, n: int, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def order(df: pandas.DataFrame, order_expr: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def project(df: pandas.DataFrame, project_expr: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def write_csv(df: pandas.DataFrame, file_name: str, connection: DuckDBPyConnection = ...) -> None: ... -def read_json( - file_name: str, - columns: Optional[Dict[str,str]] = None, - sample_size: Optional[int] = None, - maximum_depth: Optional[int] = None, - format: Optional[str] = None, - records: Optional[str] = None, - connection: DuckDBPyConnection = ... -) -> DuckDBPyRelation: ... -def read_csv( - path_or_buffer: Union[str, StringIO, TextIOBase], - header: Optional[bool | int] = None, - compression: Optional[str] = None, - sep: Optional[str] = None, - delimiter: Optional[str] = None, - dtype: Optional[Dict[str, str] | List[str]] = None, - na_values: Optional[str] = None, - skiprows: Optional[int] = None, - quotechar: Optional[str] = None, - escapechar: Optional[str] = None, - encoding: Optional[str] = None, - parallel: Optional[bool] = None, - date_format: Optional[str] = None, - timestamp_format: Optional[str] = None, - sample_size: Optional[int] = None, - all_varchar: Optional[bool] = None, - normalize_names: Optional[bool] = None, - filename: Optional[bool] = None, - connection: DuckDBPyConnection = ... -) -> DuckDBPyRelation: ... -def from_csv_auto( - name: str, - header: Optional[bool | int] = None, - compression: Optional[str] = None, - sep: Optional[str] = None, - delimiter: Optional[str] = None, - dtype: Optional[Dict[str, str] | List[str]] = None, - na_values: Optional[str] = None, - skiprows: Optional[int] = None, - quotechar: Optional[str] = None, - escapechar: Optional[str] = None, - encoding: Optional[str] = None, - parallel: Optional[bool] = None, - date_format: Optional[str] = None, - timestamp_format: Optional[str] = None, - sample_size: Optional[int] = None, - all_varchar: Optional[bool] = None, - normalize_names: Optional[bool] = None, - filename: Optional[bool] = None, - null_padding: Optional[bool] = None, - connection: DuckDBPyConnection = ... -) -> DuckDBPyRelation: ... -def append(table_name: str, df: pandas.DataFrame, connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... -def arrow(rows_per_batch: int = ..., connection: DuckDBPyConnection = ...) -> pyarrow.lib.Table: ... -def begin(connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... -def close(connection: DuckDBPyConnection = ...) -> None: ... -def commit(connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... -def cursor(connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... -def df(connection: DuckDBPyConnection = ...) -> pandas.DataFrame: ... -def description(connection: DuckDBPyConnection = ...) -> Optional[List[Any]]: ... -def rowcount(connection: DuckDBPyConnection = ...) -> int: ... -def duplicate(connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... -def execute(query: str, parameters: object = ..., multiple_parameter_sets: bool = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... -def executemany(query: str, parameters: object = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... -def fetch_arrow_table(rows_per_batch: int = ..., connection: DuckDBPyConnection = ...) -> pyarrow.lib.Table: ... -def fetch_df(*args, connection: DuckDBPyConnection = ..., **kwargs) -> pandas.DataFrame: ... -def fetch_df_chunk(*args, connection: DuckDBPyConnection = ..., **kwargs) -> pandas.DataFrame: ... -def fetch_record_batch(rows_per_batch: int = ..., connection: DuckDBPyConnection = ...) -> pyarrow.lib.RecordBatchReader: ... -def fetchall(connection: DuckDBPyConnection = ...) -> List[Any]: ... -def fetchdf(*args, connection: DuckDBPyConnection = ..., **kwargs) -> pandas.DataFrame: ... -def fetchmany(size: int = ..., connection: DuckDBPyConnection = ...) -> List[Any]: ... -def fetchnumpy(connection: DuckDBPyConnection = ...) -> dict: ... -def fetchone(connection: DuckDBPyConnection = ...) -> Optional[tuple]: ... +def aggregate(df: object, aggr_expr: str, group_expr: str = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def alias(df: object, alias: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def arrow(arrow_object: object, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def connect(database: str = ..., read_only: bool = ..., config: dict = ..., check_same_thread: bool = ...) -> DuckDBPyConnection: ... +def df(df: object, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def distinct(df: object, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def filter(df: object, filter_expr: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... def from_arrow(arrow_object: object, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def from_df(df: pandas.DataFrame = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -@overload -def read_parquet(file_glob: str, binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def from_csv_auto(file_name: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def from_df(df: object, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... @overload -def read_parquet(file_globs: List[str], binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def from_parquet(file_name: str, binary_as_string: bool, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... @overload -def from_parquet(file_glob: str, binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -@overload -def from_parquet(file_globs: List[str], binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def from_parquet(file_name: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def from_query(query: str, alias: str = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... def from_substrait(proto: bytes, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... def get_substrait(query: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def get_substrait_json(query: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def get_table_names(query: str, connection: DuckDBPyConnection = ...) -> Set[str]: ... -def install_extension(*args, connection: DuckDBPyConnection = ..., **kwargs) -> None: ... -def interrupt(connection: DuckDBPyConnection = ...) -> None: ... -def list_filesystems(connection: DuckDBPyConnection = ...) -> List[Any]: ... -def filesystem_is_registered(name: str, connection: DuckDBPyConnection = ...) -> bool: ... -def load_extension(extension: str, connection: DuckDBPyConnection = ...) -> None: ... -def pl(rows_per_batch: int = ..., connection: DuckDBPyConnection = ...) -> polars.DataFrame: ... -def torch(connection: DuckDBPyConnection = ...) -> dict: ... -def tf(self, connection: DuckDBPyConnection = ...) -> dict: ... -def register(view_name: str, python_object: object, connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... -def remove_function(name: str, connection : DuckDBPyConnection = ...) -> DuckDBPyConnection: ... -def create_function( - name: str, - func: Callable, - args: Optional[List[DuckDBPyType]] = None, - return_type: Optional[DuckDBPyType] = None, - vectorized: Optional[bool] = False, - null_handling: Optional[FunctionNullHandling] = FunctionNullHandling.DEFAULT, - exception_handling: Optional[PythonExceptionHandling] = PythonExceptionHandling.DEFAULT, - side_effects: Optional[bool] = False, - connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... -def register_filesystem(filesystem: fsspec.AbstractFileSystem, connection: DuckDBPyConnection = ...) -> None: ... -def rollback(connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... - -def query(query: str, connection: DuckDBPyConnection = ..., **kwargs) -> DuckDBPyRelation: ... -def sql(query: str, connection: DuckDBPyConnection = ..., **kwargs) -> DuckDBPyRelation: ... -def from_query(query: str, connection: DuckDBPyConnection = ..., **kwargs) -> DuckDBPyRelation: ... - -def table(table_name: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def table_function(name: str, parameters: object = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def unregister(view_name: str, connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... -def query_df(df: pandas.DataFrame, virtual_table_name: str, sql_query: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def unregister_filesystem(name: str, connection: DuckDBPyConnection = ...) -> None: ... -def tokenize(query: str) -> List[Any]: ... +def limit(df: object, n: int, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def order(df: object, order_expr: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def project(df: object, project_expr: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def query(query: str, alias: str = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def query_df(df: object, virtual_table_name: str, sql_query: str, connection: DuckDBPyConnection = ...) -> DuckDBPyResult: ... +def tokenize(query: str) -> object: ... def values(values: object, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def view(view_name: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def sqltype(type_str: str, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... -def dtype(type_str: str, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... -def type(type_str: str, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... -def struct_type(fields: Union[Dict[str, DuckDBPyType], List[str]], connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... -def row_type(fields: Union[Dict[str, DuckDBPyType], List[str]], connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... -def union_type(members: Union[Dict[str, DuckDBPyType], List[str]], connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... -def string_type(collation: str = "", connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... -def enum_type(name: str, type: DuckDBPyType, values: List[Any], connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... -def decimal_type(width: int, scale: int, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... -def array_type(type: DuckDBPyType, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... -def list_type(type: DuckDBPyType, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... -def map_type(key: DuckDBPyType, value: DuckDBPyType, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... +def write_csv(df: object, file_name: str, connection: DuckDBPyConnection = ...) -> None: ... diff --git a/tools/pythonpkg/duckdb-stubs/functional/__init__.pyi b/tools/pythonpkg/duckdb-stubs/functional/__init__.pyi deleted file mode 100644 index 63116386c3ea..000000000000 --- a/tools/pythonpkg/duckdb-stubs/functional/__init__.pyi +++ /dev/null @@ -1,33 +0,0 @@ -from .. import DuckDBPyConnection - -from typing import Dict - -SPECIAL: FunctionNullHandling -DEFAULT: FunctionNullHandling - -NATIVE: PythonUDFType -ARROW: PythonUDFType - -class FunctionNullHandling: - DEFAULT: FunctionNullHandling - SPECIAL: FunctionNullHandling - def __int__(self) -> int: ... - def __index__(self) -> int: ... - @property - def __members__(self) -> Dict[str, FunctionNullHandling]: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -class PythonUDFType: - NATIVE: PythonUDFType - ARROW: PythonUDFType - def __int__(self) -> int: ... - def __index__(self) -> int: ... - @property - def __members__(self) -> Dict[str, PythonUDFType]: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... diff --git a/tools/pythonpkg/duckdb-stubs/typing/__init__.pyi b/tools/pythonpkg/duckdb-stubs/typing/__init__.pyi deleted file mode 100644 index 139ab13ce87c..000000000000 --- a/tools/pythonpkg/duckdb-stubs/typing/__init__.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from .. import DuckDBPyConnection -from typing import List - -SQLNULL: DuckDBPyType -BOOLEAN: DuckDBPyType -TINYINT: DuckDBPyType -UTINYINT: DuckDBPyType -SMALLINT: DuckDBPyType -USMALLINT: DuckDBPyType -INTEGER: DuckDBPyType -UINTEGER: DuckDBPyType -BIGINT: DuckDBPyType -UBIGINT: DuckDBPyType -HUGEINT: DuckDBPyType -UUID: DuckDBPyType -FLOAT: DuckDBPyType -DOUBLE: DuckDBPyType -DATE: DuckDBPyType -TIMESTAMP: DuckDBPyType -TIMESTAMP_MS: DuckDBPyType -TIMESTAMP_NS: DuckDBPyType -TIMESTAMP_S: DuckDBPyType -TIME: DuckDBPyType -TIME_TZ: DuckDBPyType -TIMESTAMP_TZ: DuckDBPyType -VARCHAR: DuckDBPyType -BLOB: DuckDBPyType -BIT: DuckDBPyType -INTERVAL: DuckDBPyType - -class DuckDBPyType: - def __init__(self, type_str: str, connection: DuckDBPyConnection = ...) -> None: ... - def __repr__(self) -> str: ... - def __eq__(self, other) -> bool: ... - def __getattr__(self, name: str): DuckDBPyType - def __getitem__(self, name: str): DuckDBPyType \ No newline at end of file diff --git a/tools/pythonpkg/duckdb-stubs/value/__init__.pyi b/tools/pythonpkg/duckdb-stubs/value/__init__.pyi deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/tools/pythonpkg/duckdb-stubs/value/constant/__init__.pyi b/tools/pythonpkg/duckdb-stubs/value/constant/__init__.pyi deleted file mode 100644 index 74f654939192..000000000000 --- a/tools/pythonpkg/duckdb-stubs/value/constant/__init__.pyi +++ /dev/null @@ -1,116 +0,0 @@ -from ... import DuckDBPyConnection -from ...typing import DuckDBPyType -from typing import Any - -class NullValue(Value): - def __init__(self) -> None: ... - def __repr__(self) -> str: ... - -class BooleanValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class UnsignedBinaryValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class UnsignedShortValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class UnsignedIntegerValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class UnsignedLongValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class BinaryValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class ShortValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class IntegerValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class LongValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class HugeIntegerValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class FloatValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class DoubleValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class DecimalValue(Value): - def __init__(self, object: Any, width: int, scale: int) -> None: ... - def __repr__(self) -> str: ... - -class StringValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class UUIDValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class BitValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class BlobValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class DateValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class IntervalValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class TimestampValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class TimestampSecondValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class TimestampMilisecondValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class TimestampNanosecondValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class TimestampTimeZoneValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class TimeValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - -class TimeTimeZoneValue(Value): - def __init__(self, object: Any) -> None: ... - def __repr__(self) -> str: ... - - -class Value: - def __init__(self, object: Any, type: DuckDBPyType) -> None: ... - def __repr__(self) -> str: ... From e7cee6c1dbe28b663cc2bc39c15a4c25368790e8 Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Tue, 10 Oct 2023 10:30:05 +0200 Subject: [PATCH 19/26] Revert "run make format fix a regenerate_python_stubs" This reverts commit 07077611e04bac8b368d4f4180c40c9ca70a6c2a. --- tools/pythonpkg/duckdb-stubs/__init__.pyi | 712 +++++++++++++++--- .../duckdb-stubs/functional/__init__.pyi | 33 + .../duckdb-stubs/typing/__init__.pyi | 36 + .../pythonpkg/duckdb-stubs/value/__init__.pyi | 0 .../duckdb-stubs/value/constant/__init__.pyi | 116 +++ 5 files changed, 801 insertions(+), 96 deletions(-) create mode 100644 tools/pythonpkg/duckdb-stubs/functional/__init__.pyi create mode 100644 tools/pythonpkg/duckdb-stubs/typing/__init__.pyi create mode 100644 tools/pythonpkg/duckdb-stubs/value/__init__.pyi create mode 100644 tools/pythonpkg/duckdb-stubs/value/constant/__init__.pyi diff --git a/tools/pythonpkg/duckdb-stubs/__init__.pyi b/tools/pythonpkg/duckdb-stubs/__init__.pyi index 17b962f5def8..79d9f821629b 100644 --- a/tools/pythonpkg/duckdb-stubs/__init__.pyi +++ b/tools/pythonpkg/duckdb-stubs/__init__.pyi @@ -3,10 +3,56 @@ # generated. These should be annotated with a comment like # # stubgen override # to help the sanity of maintainers. -from typing import Any, ClassVar -from typing import overload -_clean_default_connection: PyCapsule +import duckdb.typing as typing +import duckdb.functional as functional +from duckdb.typing import DuckDBPyType +from duckdb.functional import FunctionNullHandling, PythonUDFType +from duckdb.value.constant import ( + Value, + NullValue, + BooleanValue, + UnsignedBinaryValue, + UnsignedShortValue, + UnsignedIntegerValue, + UnsignedLongValue, + BinaryValue, + ShortValue, + IntegerValue, + LongValue, + HugeIntegerValue, + FloatValue, + DoubleValue, + DecimalValue, + StringValue, + UUIDValue, + BitValue, + BlobValue, + DateValue, + IntervalValue, + TimestampValue, + TimestampSecondValue, + TimestampMilisecondValue, + TimestampNanosecondValue, + TimestampTimeZoneValue, + TimeValue, + TimeTimeZoneValue, +) + +# We also run this in python3.7, where this is needed +from typing_extensions import Literal +# stubgen override - missing import of Set +from typing import Any, ClassVar, Set, Optional, Callable +from io import StringIO, TextIOBase + +from typing import overload, Dict, List, Union +import pandas +# stubgen override - unfortunately we need this for version checks +import sys +import fsspec +import pyarrow.lib +import polars +# stubgen override - This should probably not be exposed apilevel: str comment: token_type default_connection: DuckDBPyConnection @@ -17,139 +63,488 @@ operator: token_type paramstyle: str string_const: token_type threadsafety: int +__standard_vector_size__: int +STANDARD: ExplainType +ANALYZE: ExplainType +DEFAULT: PythonExceptionHandling +RETURN_NULL: PythonExceptionHandling +ROWS: RenderMode +COLUMNS: RenderMode + +__version__: str + +__interactive__: bool +__jupyter__: bool + +class BinderException(ProgrammingError): ... + +class CastException(DataError): ... + +class CatalogException(ProgrammingError): ... + +class ConnectionException(OperationalError): ... + +class ConstraintException(IntegrityError): ... + +class ConversionException(DataError): ... + +class DataError(Error): ... + +class ExplainType: + STANDARD: ExplainType + ANALYZE: ExplainType + def __int__(self) -> int: ... + def __index__(self) -> int: ... + @property + def __members__(self) -> Dict[str, ExplainType]: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class RenderMode: + ROWS: RenderMode + COLUMNS: RenderMode + def __int__(self) -> int: ... + def __index__(self) -> int: ... + @property + def __members__(self) -> Dict[str, RenderMode]: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class PythonExceptionHandling: + DEFAULT: PythonExceptionHandling + RETURN_NULL: PythonExceptionHandling + def __int__(self) -> int: ... + def __index__(self) -> int: ... + @property + def __members__(self) -> Dict[str, PythonExceptionHandling]: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class Expression: + def __init__(self, *args, **kwargs) -> None: ... + def __neg__(self) -> "Expression": ... + + def __add__(self, expr: "Expression") -> "Expression": ... + def __radd__(self, expr: "Expression") -> "Expression": ... + + def __sub__(self, expr: "Expression") -> "Expression": ... + def __rsub__(self, expr: "Expression") -> "Expression": ... + + def __mul__(self, expr: "Expression") -> "Expression": ... + def __rmul__(self, expr: "Expression") -> "Expression": ... + + def __div__(self, expr: "Expression") -> "Expression": ... + def __rdiv__(self, expr: "Expression") -> "Expression": ... + + def __truediv__(self, expr: "Expression") -> "Expression": ... + def __rtruediv__(self, expr: "Expression") -> "Expression": ... + + def __floordiv__(self, expr: "Expression") -> "Expression": ... + def __rfloordiv__(self, expr: "Expression") -> "Expression": ... + + def __mod__(self, expr: "Expression") -> "Expression": ... + def __rmod__(self, expr: "Expression") -> "Expression": ... + + def __pow__(self, expr: "Expression") -> "Expression": ... + def __rpow__(self, expr: "Expression") -> "Expression": ... + + def __and__(self, expr: "Expression") -> "Expression": ... + def __rand__(self, expr: "Expression") -> "Expression": ... + def __or__(self, expr: "Expression") -> "Expression": ... + def __ror__(self, expr: "Expression") -> "Expression": ... + def __invert__(self) -> "Expression": ... + + def __eq__(# type: ignore[override] + self, expr: "Expression") -> "Expression": ... + def __ne__(# type: ignore[override] + self, expr: "Expression") -> "Expression": ... + def __gt__(self, expr: "Expression") -> "Expression": ... + def __ge__(self, expr: "Expression") -> "Expression": ... + def __lt__(self, expr: "Expression") -> "Expression": ... + def __le__(self, expr: "Expression") -> "Expression": ... + + def show(self, max_width: Optional[int] = None, max_rows: Optional[int] = None, max_col_width: Optional[int] = None, null_value: Optional[str] = None, render_mode: Optional[RenderMode] = None) -> None: ... + def __repr__(self) -> str: ... + def alias(self, alias: str) -> None: ... + def when(self, condition: "Expression", value: "Expression") -> "Expression": ... + def otherwise(self, value: "Expression") -> "Expression": ... + def cast(self, type: DuckDBPyType) -> "Expression": ... + def asc(self) -> "Expression": ... + def desc(self) -> "Expression": ... + def nulls_first(self) -> "Expression": ... + def nulls_last(self) -> "Expression": ... + def isin(self, *cols: "Expression") -> "Expression": ... + def isnotin(self, *cols: "Expression") -> "Expression": ... + +def StarExpression(exclude: Optional[List[str]]) -> Expression: ... +def ColumnExpression(column: str) -> Expression: ... +def ConstantExpression(val: Any) -> Expression: ... +def CaseExpression(condition: Expression, value: Expression) -> Expression: ... +def FunctionExpression(function: str, *cols: Expression) -> Expression: ... class DuckDBPyConnection: def __init__(self, *args, **kwargs) -> None: ... - def append(self, table_name: str, df: object) -> DuckDBPyConnection: ... - def arrow(self, chunk_size: int = ...) -> object: ... + def append(self, table_name: str, df: pandas.DataFrame) -> DuckDBPyConnection: ... + def arrow(self, rows_per_batch: int = ...) -> pyarrow.lib.Table: ... def begin(self) -> DuckDBPyConnection: ... def close(self) -> None: ... def commit(self) -> DuckDBPyConnection: ... def cursor(self) -> DuckDBPyConnection: ... - @overload - def df(self) -> object: ... - @overload - def df(self, df: object) -> DuckDBPyRelation: ... - @overload - def df(aliasoffrom_df) -> Any: ... + def df(self) -> pandas.DataFrame: ... def duplicate(self) -> DuckDBPyConnection: ... def execute(self, query: str, parameters: object = ..., multiple_parameter_sets: bool = ...) -> DuckDBPyConnection: ... def executemany(self, query: str, parameters: object = ...) -> DuckDBPyConnection: ... - def fetch_arrow_table(self, chunk_size: int = ...) -> object: ... - def fetch_df(self) -> object: ... - def fetch_df_chunk(self, vectors_per_chunk: int = ...) -> object: ... - def fetch_record_batch(self, chunk_size: int = ...) -> object: ... - def fetchall(self) -> list: ... - def fetchdf(self) -> object: ... + def fetch_arrow_table(self, rows_per_batch: int = ...) -> pyarrow.lib.Table: ... + def fetch_df(self, *args, **kwargs) -> pandas.DataFrame: ... + def fetch_df_chunk(self, *args, **kwargs) -> pandas.DataFrame: ... + def fetch_record_batch(self, rows_per_batch: int = ...) -> pyarrow.lib.RecordBatchReader: ... + def fetchall(self) -> List[Any]: ... + def fetchdf(self, *args, **kwargs) -> pandas.DataFrame: ... + def fetchmany(self, size: int = ...) -> List[Any]: ... def fetchnumpy(self) -> dict: ... - def fetchone(self) -> object: ... - def from_arrow(self, arrow_object: object, rows_per_thread: int = ...) -> DuckDBPyRelation: ... - def from_csv_auto(self, file_name: str) -> DuckDBPyRelation: ... - def from_df(self, df: object = ...) -> DuckDBPyRelation: ... - def from_parquet(self, file_name: str, binary_as_string: bool = ...) -> DuckDBPyRelation: ... - def from_query(self, query: str, alias: str = ...) -> DuckDBPyRelation: ... + def fetchone(self) -> Optional[tuple]: ... + def from_arrow(self, arrow_object: object) -> DuckDBPyRelation: ... + def read_json( + self, + file_name: str, + columns: Optional[Dict[str,str]] = None, + sample_size: Optional[int] = None, + maximum_depth: Optional[int] = None, + records: Optional[str] = None, + format: Optional[str] = None + ) -> DuckDBPyRelation: ... + def read_csv( + self, + path_or_buffer: Union[str, StringIO, TextIOBase], + header: Optional[bool | int] = None, + compression: Optional[str] = None, + sep: Optional[str] = None, + delimiter: Optional[str] = None, + dtype: Optional[Dict[str, str] | List[str]] = None, + na_values: Optional[str] = None, + skiprows: Optional[int] = None, + quotechar: Optional[str] = None, + escapechar: Optional[str] = None, + encoding: Optional[str] = None, + parallel: Optional[bool] = None, + date_format: Optional[str] = None, + timestamp_format: Optional[str] = None, + sample_size: Optional[int] = None, + all_varchar: Optional[bool] = None, + normalize_names: Optional[bool] = None, + filename: Optional[bool] = None, + null_padding: Optional[bool] = None, + names: Optional[List[str]] = None + ) -> DuckDBPyRelation: ... + def from_csv_auto( + self, + path_or_buffer: Union[str, StringIO, TextIOBase], + header: Optional[bool | int] = None, + compression: Optional[str] = None, + sep: Optional[str] = None, + delimiter: Optional[str] = None, + dtype: Optional[Dict[str, str] | List[str]] = None, + na_values: Optional[str] = None, + skiprows: Optional[int] = None, + quotechar: Optional[str] = None, + escapechar: Optional[str] = None, + encoding: Optional[str] = None, + parallel: Optional[bool] = None, + date_format: Optional[str] = None, + timestamp_format: Optional[str] = None, + sample_size: Optional[int] = None, + all_varchar: Optional[bool] = None, + normalize_names: Optional[bool] = None, + filename: Optional[bool] = None, + null_padding: Optional[bool] = None, + names: Optional[List[str]] = None + ) -> DuckDBPyRelation: ... + def from_df(self, df: pandas.DataFrame = ...) -> DuckDBPyRelation: ... + @overload + def read_parquet(self, file_glob: str, binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ...) -> DuckDBPyRelation: ... + @overload + def read_parquet(self, file_globs: List[str], binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ...) -> DuckDBPyRelation: ... + @overload + def from_parquet(self, file_glob: str, binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ...) -> DuckDBPyRelation: ... + @overload + def from_parquet(self, file_globs: List[str], binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ...) -> DuckDBPyRelation: ... def from_substrait(self, proto: bytes) -> DuckDBPyRelation: ... def get_substrait(self, query: str) -> DuckDBPyRelation: ... + def get_substrait_json(self, query: str) -> DuckDBPyRelation: ... + def from_substrait_json(self, json: str) -> DuckDBPyRelation: ... def get_table_names(self, query: str) -> Set[str]: ... - def query(self, query: str, alias: str = ...) -> DuckDBPyRelation: ... - def register(self, view_name: str, python_object: object, rows_per_thread: int = ...) -> DuckDBPyConnection: ... + def install_extension(self, *args, **kwargs) -> None: ... + def interrupt(self) -> None: ... + def list_filesystems(self) -> List[Any]: ... + def filesystem_is_registered(self, name: str) -> bool: ... + def load_extension(self, extension: str) -> None: ... + def pl(self, rows_per_batch: int = ..., connection: DuckDBPyConnection = ...) -> polars.DataFrame: ... + def torch(self, connection: DuckDBPyConnection = ...) -> dict: ... + def tf(self, connection: DuckDBPyConnection = ...) -> dict: ... + + def from_query(self, query: str, **kwargs) -> DuckDBPyRelation: ... + def query(self, query: str, **kwargs) -> DuckDBPyRelation: ... + def sql(self, query: str, **kwargs) -> DuckDBPyRelation: ... + + def register(self, view_name: str, python_object: object) -> DuckDBPyConnection: ... + def remove_function(self, name: str) -> DuckDBPyConnection: ... + def create_function( + self, + name: str, + func: Callable, + args: Optional[List[DuckDBPyType]] = None, + return_type: Optional[DuckDBPyType] = None, + vectorized: Optional[bool] = False, + null_handling: Optional[FunctionNullHandling] = FunctionNullHandling.DEFAULT, + exception_handling: Optional[PythonExceptionHandling] = PythonExceptionHandling.DEFAULT, + side_effects: Optional[bool] = False) -> DuckDBPyConnection: ... + def register_filesystem(self, filesystem: fsspec.AbstractFileSystem) -> None: ... def rollback(self) -> DuckDBPyConnection: ... def table(self, table_name: str) -> DuckDBPyRelation: ... def table_function(self, name: str, parameters: object = ...) -> DuckDBPyRelation: ... def unregister(self, view_name: str) -> DuckDBPyConnection: ... + def unregister_filesystem(self, name: str) -> None: ... def values(self, values: object) -> DuckDBPyRelation: ... def view(self, view_name: str) -> DuckDBPyRelation: ... - def __enter__(self, database: str = ..., read_only: bool = ..., config: dict = ..., check_same_thread: bool = ...) -> DuckDBPyConnection: ... - def __exit__(self, exc_type: object, exc: object, traceback: object) -> bool: ... + def sqltype(self, type_str: str) -> DuckDBPyType: ... + def dtype(self, type_str: str) -> DuckDBPyType: ... + def type(self, type_str: str) -> DuckDBPyType: ... + def struct_type(self, fields: Union[Dict[str, DuckDBPyType], List[str]]) -> DuckDBPyType: ... + def row_type(self, fields: Union[Dict[str, DuckDBPyType], List[str]]) -> DuckDBPyType: ... + def union_type(self, members: Union[Dict[str, DuckDBPyType], List[str]]) -> DuckDBPyType: ... + def string_type(self, collation: str = "") -> DuckDBPyType: ... + def enum_type(self, name: str, type: DuckDBPyType, values: List[Any]) -> DuckDBPyType: ... + def decimal_type(self, width: int, scale: int) -> DuckDBPyType: ... + def array_type(self, type: DuckDBPyType) -> DuckDBPyType: ... + def list_type(self, type: DuckDBPyType) -> DuckDBPyType: ... + def map_type(self, key: DuckDBPyType, value: DuckDBPyType) -> DuckDBPyType: ... + def __enter__(self) -> DuckDBPyConnection: ... + def __exit__(self, exc_type: object, exc: object, traceback: object) -> None: ... + @property + def description(self) -> Optional[List[Any]]: ... @property - def description(self) -> object: ... + def rowcount(self) -> int: ... class DuckDBPyRelation: + def close(self) -> None: ... + def __getattr__(self, name: str) -> DuckDBPyRelation: ... + def __getitem__(self, name: str) -> DuckDBPyRelation: ... def __init__(self, *args, **kwargs) -> None: ... - def abs(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... + def __contains__(self, name: str) -> bool: ... def aggregate(self, aggr_expr: str, group_expr: str = ...) -> DuckDBPyRelation: ... + def any_value(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... def apply(self, function_name: str, function_aggr: str, group_expr: str = ..., function_parameter: str = ..., projected_columns: str = ...) -> DuckDBPyRelation: ... - def arrow(self, batch_size: int = ...) -> object: ... - def count(self, count_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... + def arg_max(self, arg_column: str, value_column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def arg_min(self, arg_column: str, value_column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def arrow(self, batch_size: int = ...) -> pyarrow.lib.Table: ... + def avg(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def bit_and(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def bit_or(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def bit_xor(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def bitstring_agg(self, column: str, min: Optional[int], max: Optional[int], groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def bool_and(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def bool_or(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def count(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... def create(self, table_name: str) -> None: ... def create_view(self, view_name: str, replace: bool = ...) -> DuckDBPyRelation: ... - def cummax(self, aggregation_columns: str) -> DuckDBPyRelation: ... - def cummin(self, aggregation_columns: str) -> DuckDBPyRelation: ... - def cumprod(self, aggregation_columns: str) -> DuckDBPyRelation: ... - def cumsum(self, aggregation_columns: str) -> DuckDBPyRelation: ... + def cume_dist(self, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def dense_rank(self, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... def describe(self) -> DuckDBPyRelation: ... - def df(self) -> object: ... + def df(self, *args, **kwargs) -> pandas.DataFrame: ... def distinct(self) -> DuckDBPyRelation: ... def except_(self, other_rel: DuckDBPyRelation) -> DuckDBPyRelation: ... - def execute(self, *args, **kwargs) -> Any: ... - def fetchall(self) -> object: ... - def fetchone(self) -> object: ... - def filter(self, filter_expr: str) -> DuckDBPyRelation: ... + def execute(self, *args, **kwargs) -> DuckDBPyRelation: ... + def explain(self, type: Optional[Literal['standard', 'analyze'] | int] = 'standard') -> str: ... + def favg(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def fetchall(self) -> List[Any]: ... + def fetchmany(self, size: int = ...) -> List[Any]: ... + def fetchnumpy(self) -> dict: ... + def fetchone(self) -> Optional[tuple]: ... + def fetchdf(self, *args, **kwargs) -> Any: ... + def fetch_arrow_reader(self, batch_size: int = ...) -> pyarrow.lib.RecordBatchReader: ... + def fetch_arrow_table(self, rows_per_batch: int = ...) -> pyarrow.lib.Table: ... + def filter(self, filter_expr: Union[Expression, str]) -> DuckDBPyRelation: ... + def first(self, column: str, window_spec: str, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... + def first_value(self, column: str, window_spec: str, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... + def fsum(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def geomean(self, column: str, groups: str, projected_columns: str) -> DuckDBPyRelation: ... + def histogram(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... def insert(self, values: object) -> None: ... def insert_into(self, table_name: str) -> None: ... def intersect(self, other_rel: DuckDBPyRelation) -> DuckDBPyRelation: ... def join(self, other_rel: DuckDBPyRelation, condition: str, how: str = ...) -> DuckDBPyRelation: ... - def kurt(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... + def lag(self, column: str, window_spec: str, offset: int, default_value: str, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... + def last(self, column: str, window_spec: str, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... + def last_value(self, column: str, window_spec: str, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... + def lead(self, column: str, window_spec: str, offset: int, default_value: str, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... def limit(self, n: int, offset: int = ...) -> DuckDBPyRelation: ... - def mad(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... - def map(self, map_function: function) -> DuckDBPyRelation: ... + def list(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def map(self, map_function: function, schema: Optional[Dict[str, DuckDBPyType]]) -> DuckDBPyRelation: ... def max(self, max_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... def mean(self, mean_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... def median(self, median_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... def min(self, min_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... def mode(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... + def n_tile(self, window_spec: str, num_buckets: int, projected_columns: str) -> DuckDBPyRelation: ... + def nth_value(self, column: str, window_spec: str, offset: int, ignore_nulls: bool, projected_columns: str) -> DuckDBPyRelation: ... def order(self, order_expr: str) -> DuckDBPyRelation: ... - def prod(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... - def project(self, project_expr: str) -> DuckDBPyRelation: ... + def sort(self, *cols: Expression) -> DuckDBPyRelation: ... + def project(self, *cols: Union[str, Expression]) -> DuckDBPyRelation: ... + def select(self, *cols: Union[str, Expression]) -> DuckDBPyRelation: ... + def percent_rank(self, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def pl(self, rows_per_batch: int = ..., connection: DuckDBPyConnection = ...) -> polars.DataFrame: ... + def product(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... def quantile(self, q: str, quantile_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... - def query(self, *args, **kwargs) -> Any: ... - def record_batch(self, batch_size: int = ...) -> object: ... - def sem(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... + def quantile_cont(self, column: str, q: Union[float, List[float]], groups: str, projected_columns: str) -> DuckDBPyRelation: ... + def quantile_disc(self, column: str, q: Union[float, List[float]], groups: str, projected_columns: str) -> DuckDBPyRelation: ... + def query(self, virtual_table_name: str, sql_query: str) -> DuckDBPyRelation: ... + def rank(self, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def rank_dense(self, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def record_batch(self, batch_size: int = ...) -> pyarrow.lib.RecordBatchReader: ... + def row_number(self, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def select_types(self, types: List[Union[str, DuckDBPyType]]) -> DuckDBPyRelation: ... + def select_dtypes(self, types: List[Union[str, DuckDBPyType]]) -> DuckDBPyRelation: ... def set_alias(self, alias: str) -> DuckDBPyRelation: ... - def skew(self, aggregation_columns: str, group_columns: str = ...) -> DuckDBPyRelation: ... - def std(self, std_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... + def show(self) -> None: ... + def sql_query(self) -> str: ... + def std(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def stddev(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def stddev_pop(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def stddev_samp(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def string_agg(self, column: str, sep: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... def sum(self, sum_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... - def to_arrow_table(self, batch_size: int = ...) -> object: ... - def to_df(self) -> object: ... - @overload + def to_arrow_table(self, batch_size: int = ...) -> pyarrow.lib.Table: ... + def to_csv( + self, + file_name: str, + sep: Optional[str], + na_rep: Optional[str], + header: Optional[bool], + quotechar: Optional[str], + escapechar: Optional[str], + date_format: Optional[str], + timestamp_format: Optional[str], + quoting: Optional[str | int], + encoding: Optional[str], + compression: Optional[str] + ) -> None: ... + def to_df(self, *args, **kwargs) -> pandas.DataFrame: ... + def to_parquet( + self, + file_name: str, + compression: Optional[str] + ) -> None: ... + def to_table(self, table_name: str) -> None: ... + def to_view(self, view_name: str, replace: bool = ...) -> DuckDBPyRelation: ... + def torch(self, connection: DuckDBPyConnection = ...) -> dict: ... + def tf(self, connection: DuckDBPyConnection = ...) -> dict: ... def union(self, union_rel: DuckDBPyRelation) -> DuckDBPyRelation: ... - @overload - def union(self, arg0: DuckDBPyRelation) -> DuckDBPyRelation: ... def unique(self, unique_aggr: str) -> DuckDBPyRelation: ... - def value_counts(self, value_counts_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... - def var(self, var_aggr: str, group_expr: str = ...) -> DuckDBPyRelation: ... - def write_csv(self, file_name: str) -> None: ... + def var(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def var_pop(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def var_samp(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def variance(self, column: str, groups: str, window_spec: str, projected_columns: str) -> DuckDBPyRelation: ... + def write_csv( + self, + file_name: str, + sep: Optional[str], + na_rep: Optional[str], + header: Optional[bool], + quotechar: Optional[str], + escapechar: Optional[str], + date_format: Optional[str], + timestamp_format: Optional[str], + quoting: Optional[str | int], + encoding: Optional[str], + compression: Optional[str] + ) -> None: ... + def write_parquet( + self, + file_name: str, + compression: Optional[str] + ) -> None: ... def __len__(self) -> int: ... @property def alias(self) -> str: ... @property - def columns(self) -> list: ... + def columns(self) -> List[Any]: ... @property - def dtypes(self) -> list: ... + def dtypes(self) -> List[DuckDBPyType]: ... + @property + def description(self) -> List[Any]: ... @property def shape(self) -> tuple: ... @property def type(self) -> str: ... @property - def types(self) -> list: ... + def types(self) -> List[DuckDBPyType]: ... -class DuckDBPyResult: - def __init__(self, *args, **kwargs) -> None: ... - def arrow(self, chunk_size: int = ...) -> object: ... - def close(self) -> None: ... - def description(self) -> list: ... - def df(self) -> object: ... - def fetch_arrow_reader(self, approx_batch_size: int) -> object: ... - def fetch_arrow_table(self, chunk_size: int = ...) -> object: ... - def fetch_df(self) -> object: ... - def fetch_df_chunk(self, arg0: int) -> object: ... - def fetchall(self) -> list: ... - def fetchdf(self) -> object: ... - def fetchnumpy(self) -> dict: ... - def fetchone(self) -> object: ... +class Error(Exception): ... + +class FatalException(Error): ... + +class HTTPException(IOException): + status_code: int + body: str + reason: str + headers: Dict[str, str] + +class IOException(OperationalError): ... + +class IntegrityError(Error): ... + +class InternalError(Error): ... + +class InternalException(InternalError): ... + +class InterruptException(Error): ... + +class InvalidInputException(ProgrammingError): ... + +class InvalidTypeException(ProgrammingError): ... + +class NotImplementedException(NotSupportedError): ... + +class NotSupportedError(Error): ... + +class OperationalError(Error): ... + +class OutOfMemoryException(OperationalError): ... + +class OutOfRangeException(DataError): ... + +class ParserException(ProgrammingError): ... + +class PermissionException(Error): ... + +class ProgrammingError(Error): ... + +class SequenceException(Error): ... + +class SerializationException(OperationalError): ... + +class StandardException(Error): ... + +class SyntaxException(ProgrammingError): ... + +class TransactionException(OperationalError): ... + +class TypeMismatchException(DataError): ... + +class ValueOutOfRangeException(DataError): ... + +class Warning(Exception): ... class token_type: - __members__: ClassVar[dict] = ... # read-only + # stubgen override - these make mypy sad + #__doc__: ClassVar[str] = ... # read-only + #__members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... comment: ClassVar[token_type] = ... identifier: ClassVar[token_type] = ... @@ -161,7 +556,9 @@ class token_type: def __eq__(self, other: object) -> bool: ... def __getstate__(self) -> int: ... def __hash__(self) -> int: ... - def __index__(self) -> int: ... + # stubgen override - pybind only puts index in python >= 3.8: https://github.com/EricCousineau-TRI/pybind11/blob/54430436/include/pybind11/pybind11.h#L1789 + if sys.version_info >= (3, 7): + def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... def __setstate__(self, state: int) -> None: ... @@ -169,29 +566,152 @@ class token_type: def name(self) -> str: ... @property def value(self) -> int: ... + @property + # stubgen override - this gets removed by stubgen but it shouldn't + def __members__(self) -> object: ... + +def aggregate(df: pandas.DataFrame, aggr_expr: str, group_expr: str = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def alias(df: pandas.DataFrame, alias: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def connect(database: str = ..., read_only: bool = ..., config: dict = ...) -> DuckDBPyConnection: ... +def distinct(df: pandas.DataFrame, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def filter(df: pandas.DataFrame, filter_expr: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def from_substrait_json(jsonm: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def limit(df: pandas.DataFrame, n: int, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def order(df: pandas.DataFrame, order_expr: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def project(df: pandas.DataFrame, project_expr: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def write_csv(df: pandas.DataFrame, file_name: str, connection: DuckDBPyConnection = ...) -> None: ... +def read_json( + file_name: str, + columns: Optional[Dict[str,str]] = None, + sample_size: Optional[int] = None, + maximum_depth: Optional[int] = None, + format: Optional[str] = None, + records: Optional[str] = None, + connection: DuckDBPyConnection = ... +) -> DuckDBPyRelation: ... +def read_csv( + path_or_buffer: Union[str, StringIO, TextIOBase], + header: Optional[bool | int] = None, + compression: Optional[str] = None, + sep: Optional[str] = None, + delimiter: Optional[str] = None, + dtype: Optional[Dict[str, str] | List[str]] = None, + na_values: Optional[str] = None, + skiprows: Optional[int] = None, + quotechar: Optional[str] = None, + escapechar: Optional[str] = None, + encoding: Optional[str] = None, + parallel: Optional[bool] = None, + date_format: Optional[str] = None, + timestamp_format: Optional[str] = None, + sample_size: Optional[int] = None, + all_varchar: Optional[bool] = None, + normalize_names: Optional[bool] = None, + filename: Optional[bool] = None, + connection: DuckDBPyConnection = ... +) -> DuckDBPyRelation: ... +def from_csv_auto( + name: str, + header: Optional[bool | int] = None, + compression: Optional[str] = None, + sep: Optional[str] = None, + delimiter: Optional[str] = None, + dtype: Optional[Dict[str, str] | List[str]] = None, + na_values: Optional[str] = None, + skiprows: Optional[int] = None, + quotechar: Optional[str] = None, + escapechar: Optional[str] = None, + encoding: Optional[str] = None, + parallel: Optional[bool] = None, + date_format: Optional[str] = None, + timestamp_format: Optional[str] = None, + sample_size: Optional[int] = None, + all_varchar: Optional[bool] = None, + normalize_names: Optional[bool] = None, + filename: Optional[bool] = None, + null_padding: Optional[bool] = None, + connection: DuckDBPyConnection = ... +) -> DuckDBPyRelation: ... -def aggregate(df: object, aggr_expr: str, group_expr: str = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def alias(df: object, alias: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def arrow(arrow_object: object, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def connect(database: str = ..., read_only: bool = ..., config: dict = ..., check_same_thread: bool = ...) -> DuckDBPyConnection: ... -def df(df: object, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def distinct(df: object, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def filter(df: object, filter_expr: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def append(table_name: str, df: pandas.DataFrame, connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... +def arrow(rows_per_batch: int = ..., connection: DuckDBPyConnection = ...) -> pyarrow.lib.Table: ... +def begin(connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... +def close(connection: DuckDBPyConnection = ...) -> None: ... +def commit(connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... +def cursor(connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... +def df(connection: DuckDBPyConnection = ...) -> pandas.DataFrame: ... +def description(connection: DuckDBPyConnection = ...) -> Optional[List[Any]]: ... +def rowcount(connection: DuckDBPyConnection = ...) -> int: ... +def duplicate(connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... +def execute(query: str, parameters: object = ..., multiple_parameter_sets: bool = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... +def executemany(query: str, parameters: object = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... +def fetch_arrow_table(rows_per_batch: int = ..., connection: DuckDBPyConnection = ...) -> pyarrow.lib.Table: ... +def fetch_df(*args, connection: DuckDBPyConnection = ..., **kwargs) -> pandas.DataFrame: ... +def fetch_df_chunk(*args, connection: DuckDBPyConnection = ..., **kwargs) -> pandas.DataFrame: ... +def fetch_record_batch(rows_per_batch: int = ..., connection: DuckDBPyConnection = ...) -> pyarrow.lib.RecordBatchReader: ... +def fetchall(connection: DuckDBPyConnection = ...) -> List[Any]: ... +def fetchdf(*args, connection: DuckDBPyConnection = ..., **kwargs) -> pandas.DataFrame: ... +def fetchmany(size: int = ..., connection: DuckDBPyConnection = ...) -> List[Any]: ... +def fetchnumpy(connection: DuckDBPyConnection = ...) -> dict: ... +def fetchone(connection: DuckDBPyConnection = ...) -> Optional[tuple]: ... def from_arrow(arrow_object: object, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def from_csv_auto(file_name: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def from_df(df: object, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def from_df(df: pandas.DataFrame = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +@overload +def read_parquet(file_glob: str, binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... @overload -def from_parquet(file_name: str, binary_as_string: bool, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def read_parquet(file_globs: List[str], binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... @overload -def from_parquet(file_name: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def from_query(query: str, alias: str = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def from_parquet(file_glob: str, binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +@overload +def from_parquet(file_globs: List[str], binary_as_string: bool = ..., *, file_row_number: bool = ..., filename: bool = ..., hive_partitioning: bool = ..., union_by_name: bool = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... def from_substrait(proto: bytes, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... def get_substrait(query: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def limit(df: object, n: int, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def order(df: object, order_expr: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def project(df: object, project_expr: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def query(query: str, alias: str = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def query_df(df: object, virtual_table_name: str, sql_query: str, connection: DuckDBPyConnection = ...) -> DuckDBPyResult: ... -def tokenize(query: str) -> object: ... +def get_substrait_json(query: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def get_table_names(query: str, connection: DuckDBPyConnection = ...) -> Set[str]: ... +def install_extension(*args, connection: DuckDBPyConnection = ..., **kwargs) -> None: ... +def interrupt(connection: DuckDBPyConnection = ...) -> None: ... +def list_filesystems(connection: DuckDBPyConnection = ...) -> List[Any]: ... +def filesystem_is_registered(name: str, connection: DuckDBPyConnection = ...) -> bool: ... +def load_extension(extension: str, connection: DuckDBPyConnection = ...) -> None: ... +def pl(rows_per_batch: int = ..., connection: DuckDBPyConnection = ...) -> polars.DataFrame: ... +def torch(connection: DuckDBPyConnection = ...) -> dict: ... +def tf(self, connection: DuckDBPyConnection = ...) -> dict: ... +def register(view_name: str, python_object: object, connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... +def remove_function(name: str, connection : DuckDBPyConnection = ...) -> DuckDBPyConnection: ... +def create_function( + name: str, + func: Callable, + args: Optional[List[DuckDBPyType]] = None, + return_type: Optional[DuckDBPyType] = None, + vectorized: Optional[bool] = False, + null_handling: Optional[FunctionNullHandling] = FunctionNullHandling.DEFAULT, + exception_handling: Optional[PythonExceptionHandling] = PythonExceptionHandling.DEFAULT, + side_effects: Optional[bool] = False, + connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... +def register_filesystem(filesystem: fsspec.AbstractFileSystem, connection: DuckDBPyConnection = ...) -> None: ... +def rollback(connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... + +def query(query: str, connection: DuckDBPyConnection = ..., **kwargs) -> DuckDBPyRelation: ... +def sql(query: str, connection: DuckDBPyConnection = ..., **kwargs) -> DuckDBPyRelation: ... +def from_query(query: str, connection: DuckDBPyConnection = ..., **kwargs) -> DuckDBPyRelation: ... + +def table(table_name: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def table_function(name: str, parameters: object = ..., connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def unregister(view_name: str, connection: DuckDBPyConnection = ...) -> DuckDBPyConnection: ... +def query_df(df: pandas.DataFrame, virtual_table_name: str, sql_query: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def unregister_filesystem(name: str, connection: DuckDBPyConnection = ...) -> None: ... +def tokenize(query: str) -> List[Any]: ... def values(values: object, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... -def write_csv(df: object, file_name: str, connection: DuckDBPyConnection = ...) -> None: ... +def view(view_name: str, connection: DuckDBPyConnection = ...) -> DuckDBPyRelation: ... +def sqltype(type_str: str, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... +def dtype(type_str: str, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... +def type(type_str: str, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... +def struct_type(fields: Union[Dict[str, DuckDBPyType], List[str]], connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... +def row_type(fields: Union[Dict[str, DuckDBPyType], List[str]], connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... +def union_type(members: Union[Dict[str, DuckDBPyType], List[str]], connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... +def string_type(collation: str = "", connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... +def enum_type(name: str, type: DuckDBPyType, values: List[Any], connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... +def decimal_type(width: int, scale: int, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... +def array_type(type: DuckDBPyType, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... +def list_type(type: DuckDBPyType, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... +def map_type(key: DuckDBPyType, value: DuckDBPyType, connection: DuckDBPyConnection = ...) -> DuckDBPyType: ... diff --git a/tools/pythonpkg/duckdb-stubs/functional/__init__.pyi b/tools/pythonpkg/duckdb-stubs/functional/__init__.pyi new file mode 100644 index 000000000000..63116386c3ea --- /dev/null +++ b/tools/pythonpkg/duckdb-stubs/functional/__init__.pyi @@ -0,0 +1,33 @@ +from .. import DuckDBPyConnection + +from typing import Dict + +SPECIAL: FunctionNullHandling +DEFAULT: FunctionNullHandling + +NATIVE: PythonUDFType +ARROW: PythonUDFType + +class FunctionNullHandling: + DEFAULT: FunctionNullHandling + SPECIAL: FunctionNullHandling + def __int__(self) -> int: ... + def __index__(self) -> int: ... + @property + def __members__(self) -> Dict[str, FunctionNullHandling]: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class PythonUDFType: + NATIVE: PythonUDFType + ARROW: PythonUDFType + def __int__(self) -> int: ... + def __index__(self) -> int: ... + @property + def __members__(self) -> Dict[str, PythonUDFType]: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... diff --git a/tools/pythonpkg/duckdb-stubs/typing/__init__.pyi b/tools/pythonpkg/duckdb-stubs/typing/__init__.pyi new file mode 100644 index 000000000000..139ab13ce87c --- /dev/null +++ b/tools/pythonpkg/duckdb-stubs/typing/__init__.pyi @@ -0,0 +1,36 @@ +from .. import DuckDBPyConnection +from typing import List + +SQLNULL: DuckDBPyType +BOOLEAN: DuckDBPyType +TINYINT: DuckDBPyType +UTINYINT: DuckDBPyType +SMALLINT: DuckDBPyType +USMALLINT: DuckDBPyType +INTEGER: DuckDBPyType +UINTEGER: DuckDBPyType +BIGINT: DuckDBPyType +UBIGINT: DuckDBPyType +HUGEINT: DuckDBPyType +UUID: DuckDBPyType +FLOAT: DuckDBPyType +DOUBLE: DuckDBPyType +DATE: DuckDBPyType +TIMESTAMP: DuckDBPyType +TIMESTAMP_MS: DuckDBPyType +TIMESTAMP_NS: DuckDBPyType +TIMESTAMP_S: DuckDBPyType +TIME: DuckDBPyType +TIME_TZ: DuckDBPyType +TIMESTAMP_TZ: DuckDBPyType +VARCHAR: DuckDBPyType +BLOB: DuckDBPyType +BIT: DuckDBPyType +INTERVAL: DuckDBPyType + +class DuckDBPyType: + def __init__(self, type_str: str, connection: DuckDBPyConnection = ...) -> None: ... + def __repr__(self) -> str: ... + def __eq__(self, other) -> bool: ... + def __getattr__(self, name: str): DuckDBPyType + def __getitem__(self, name: str): DuckDBPyType \ No newline at end of file diff --git a/tools/pythonpkg/duckdb-stubs/value/__init__.pyi b/tools/pythonpkg/duckdb-stubs/value/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tools/pythonpkg/duckdb-stubs/value/constant/__init__.pyi b/tools/pythonpkg/duckdb-stubs/value/constant/__init__.pyi new file mode 100644 index 000000000000..74f654939192 --- /dev/null +++ b/tools/pythonpkg/duckdb-stubs/value/constant/__init__.pyi @@ -0,0 +1,116 @@ +from ... import DuckDBPyConnection +from ...typing import DuckDBPyType +from typing import Any + +class NullValue(Value): + def __init__(self) -> None: ... + def __repr__(self) -> str: ... + +class BooleanValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class UnsignedBinaryValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class UnsignedShortValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class UnsignedIntegerValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class UnsignedLongValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class BinaryValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class ShortValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class IntegerValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class LongValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class HugeIntegerValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class FloatValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class DoubleValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class DecimalValue(Value): + def __init__(self, object: Any, width: int, scale: int) -> None: ... + def __repr__(self) -> str: ... + +class StringValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class UUIDValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class BitValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class BlobValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class DateValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class IntervalValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class TimestampValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class TimestampSecondValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class TimestampMilisecondValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class TimestampNanosecondValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class TimestampTimeZoneValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class TimeValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + +class TimeTimeZoneValue(Value): + def __init__(self, object: Any) -> None: ... + def __repr__(self) -> str: ... + + +class Value: + def __init__(self, object: Any, type: DuckDBPyType) -> None: ... + def __repr__(self) -> str: ... From 03940a0513f9fa94f4919c516355598291a0959a Mon Sep 17 00:00:00 2001 From: Tom Ebergen Date: Tue, 10 Oct 2023 11:25:02 +0200 Subject: [PATCH 20/26] add stubs to query graph --- .../pythonpkg/duckdb/query_graph/__main__.py | 62 +++++++++++-------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/tools/pythonpkg/duckdb/query_graph/__main__.py b/tools/pythonpkg/duckdb/query_graph/__main__.py index b0f6be687d8d..974ac89e20de 100644 --- a/tools/pythonpkg/duckdb/query_graph/__main__.py +++ b/tools/pythonpkg/duckdb/query_graph/__main__.py @@ -46,37 +46,39 @@ } """ + class NodeTiming: - def __init__(self, phase, time): + def __init__(self, phase: str, time: float) -> object: self.phase = phase self.time = time # percentage is determined later. self.percentage = 0 - def calculate_percentage(self, total_time): - self.percentage = self.time/total_time + def calculate_percentage(self, total_time: float) -> None: + self.percentage = self.time / total_time - def combine_timing(l, r): + def combine_timing(l: object, r: object) -> object: # TODO: can only add timings for same-phase nodes total_time = l.time + r.time return NodeTiming(l.phase, total_time) + class AllTimings: def __init__(self): self.phase_to_timings = {} - def add_node_timing(self, node_timing): + def add_node_timing(self, node_timing: NodeTiming): if node_timing.phase in self.phase_to_timings: self.phase_to_timings[node_timing.phase].append(node_timing) return self.phase_to_timings[node_timing.phase] = [node_timing] - def get_phase_timings(self, phase): + def get_phase_timings(self, phase: str): return self.phase_to_timings[phase] - def get_summary_phase_timings(self, phase): + def get_summary_phase_timings(self, phase: str): return reduce(NodeTiming.combine_timing, self.phase_to_timings[phase]) def get_phases(self): @@ -92,15 +94,17 @@ def get_sum_of_all_timings(self): return total_timing_sum -def open_utf8(fpath, flags): +def open_utf8(fpath: str, flags: str) -> object: return open(fpath, flags, encoding="utf8") -def get_child_timings(top_node, query_timings): + +def get_child_timings(top_node: object, query_timings: object) -> str: node_timing = NodeTiming(top_node['name'], float(top_node['timing'])) query_timings.add_node_timing(node_timing) for child in top_node['children']: get_child_timings(child, query_timings) + color_map = { "HASH_JOIN": "#ffffba", "PROJECTION": "#ffb3ba", @@ -116,7 +120,8 @@ def get_child_timings(top_node, query_timings): "TOP_N": "#ffdfba" } -def get_node_body(name, result, cardinality, extra_info, timing): + +def get_node_body(name: str, result: str, cardinality: float, extra_info: str, timing: object) -> str: node_style = "" stripped_name = name.strip() if stripped_name in color_map: @@ -137,7 +142,7 @@ def get_node_body(name, result, cardinality, extra_info, timing): return body -def generate_tree_recursive(json_graph): +def generate_tree_recursive(json_graph: object) -> str: node_prefix_html = "
  • " node_suffix_html = "
  • " node_body = get_node_body(json_graph["name"], @@ -154,8 +159,9 @@ def generate_tree_recursive(json_graph): children_html += "" return node_prefix_html + node_body + children_html + node_suffix_html + # For generating the table in the top left. -def generate_timing_html(graph_json, query_timings): +def generate_timing_html(graph_json: object, query_timings: object) -> object: json_graph = json.loads(graph_json) gather_timing_information(json_graph, query_timings) total_time = float(json_graph['timing']) @@ -192,7 +198,8 @@ def generate_timing_html(graph_json, query_timings): table_body += table_end return table_head + table_body -def generate_tree_html(graph_json): + +def generate_tree_html(graph_json: object) -> str: json_graph = json.loads(graph_json) tree_prefix = "
    \n
      " tree_suffix = "
    " @@ -202,20 +209,22 @@ def generate_tree_html(graph_json): return tree_prefix + tree_body + tree_suffix -def generate_ipython(json_input): +def generate_ipython(json_input: str) -> str: from IPython.core.display import HTML html_output = generate_html(json_input, False) - return HTML(""" - ${CSS} - ${LIBRARIES} -
    - ${CHART_SCRIPT} - """.replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', html_output['chart_script']).replace('${LIBRARIES}', html_output['libraries'])) + return HTML(("\n" + " ${CSS}\n" + " ${LIBRARIES}\n" + "
    \n" + " ${CHART_SCRIPT}\n" + " ").replace("${CSS}", html_output['css']).replace('${CHART_SCRIPT}', + html_output['chart_script']).replace( + '${LIBRARIES}', html_output['libraries'])) -def generate_style_html(graph_json, include_meta_info): +def generate_style_html(graph_json: str, include_meta_info: bool) -> None: treeflex_css = "\n" css = "