pyDiffTools 0.1.30__tar.gz → 0.1.32__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. {pydifftools-0.1.30 → pydifftools-0.1.32}/MANIFEST.in +2 -0
  2. {pydifftools-0.1.30/pyDiffTools.egg-info → pydifftools-0.1.32}/PKG-INFO +10 -1
  3. {pydifftools-0.1.30 → pydifftools-0.1.32}/README.rst +9 -0
  4. {pydifftools-0.1.30 → pydifftools-0.1.32/pyDiffTools.egg-info}/PKG-INFO +10 -1
  5. {pydifftools-0.1.30 → pydifftools-0.1.32}/pyDiffTools.egg-info/SOURCES.txt +3 -0
  6. pydifftools-0.1.32/pydifftools/flowchart/comparison.py +330 -0
  7. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/flowchart/graph.py +19 -19
  8. pydifftools-0.1.32/pydifftools/flowchart/svg-pan-zoom.min.js +30 -0
  9. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/flowchart/watch_graph.py +228 -231
  10. pydifftools-0.1.32/pydifftools/flowchart/wgrph_view.js +212 -0
  11. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/git_gd_history.py +34 -16
  12. {pydifftools-0.1.30 → pydifftools-0.1.32}/pyproject.toml +5 -1
  13. {pydifftools-0.1.30 → pydifftools-0.1.32}/tests/test_git_gd_history.py +2 -0
  14. {pydifftools-0.1.30 → pydifftools-0.1.32}/LICENSE.md +0 -0
  15. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/_quarto.yml +0 -0
  16. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/__version__.txt +0 -0
  17. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/1a724af72b16f5a9e607e12b1c721645/base.ipynb +0 -0
  18. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/1b28fc9daac9081847e5161b2c546f8a/base.ipynb +0 -0
  19. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/231f64eee282fa225d1104935cf80a24/base.ipynb +0 -0
  20. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/26e56f6b0ff54851a45145157f2f0dc4/base.ipynb +0 -0
  21. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/311fabd7029ffd050d056e2f316eb50f/base.ipynb +0 -0
  22. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/57da2021e5b156ac3adf01398201c723/base.ipynb +0 -0
  23. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/62b24ea7da75011d92b0f8924faa208d/base.ipynb +0 -0
  24. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/7f1b20d69d889514ab5d1cc92e3cb14f/base.ipynb +0 -0
  25. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/86f74c8c54a87ff892d9b15dd714e8f0/base.ipynb +0 -0
  26. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/88893b4234eac2945d9d6cb2e277f186/base.ipynb +0 -0
  27. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/9a40046ada6f582ee34af00fbdbfb417/base.ipynb +0 -0
  28. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/a1bf4d270d0641ff41faf1d7cce3439a/base.ipynb +0 -0
  29. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/a3789f7d9585a781f2a1c60ce95ff10d/base.ipynb +0 -0
  30. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/be46ecba858d39ad5f0c46902ddf1c02/base.ipynb +0 -0
  31. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/d0cbc57a12f2ccce710a5afd04cc05e7/base.ipynb +0 -0
  32. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/d3e12d320b14228f701231ae32ddd7dd/base.ipynb +0 -0
  33. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/eb434f61555438d020a6970a5dbf9ee8/base.ipynb +0 -0
  34. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/f6b0e73aa7fa029134665d4dde57e096/base.ipynb +0 -0
  35. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/f719a6e4ff09873cb0ffb06ec9d232f9/base.ipynb +0 -0
  36. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/executed/feeee244a7ce3d60e1a227eb604df823/base.ipynb +0 -0
  37. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/.jupyter_cache/global.db +0 -0
  38. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/example.qmd +0 -0
  39. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/example.tex +0 -0
  40. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/index.qmd +0 -0
  41. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/subproject1/.jupyter_cache/__version__.txt +0 -0
  42. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/subproject1/.jupyter_cache/executed/ca90e4df5f4f0583df6554156a68dc7f/base.ipynb +0 -0
  43. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/subproject1/.jupyter_cache/global.db +0 -0
  44. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg +0 -0
  45. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/subproject1/independent.qmd +0 -0
  46. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/subproject1/index.qmd +0 -0
  47. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/subproject1/tasks.qmd +0 -0
  48. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/subproject1/test_include.qmd +0 -0
  49. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/subproject1/tryforerror.qmd +0 -0
  50. {pydifftools-0.1.30 → pydifftools-0.1.32}/example_notebook/project1/test_include.qmd +0 -0
  51. {pydifftools-0.1.30 → pydifftools-0.1.32}/pyDiffTools.egg-info/dependency_links.txt +0 -0
  52. {pydifftools-0.1.30 → pydifftools-0.1.32}/pyDiffTools.egg-info/entry_points.txt +0 -0
  53. {pydifftools-0.1.30 → pydifftools-0.1.32}/pyDiffTools.egg-info/requires.txt +0 -0
  54. {pydifftools-0.1.30 → pydifftools-0.1.32}/pyDiffTools.egg-info/top_level.txt +0 -0
  55. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/__init__.py +0 -0
  56. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/browser_lifecycle.py +0 -0
  57. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/check_numbers.py +0 -0
  58. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/command_line.py +0 -0
  59. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/command_registry.py +0 -0
  60. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/comment_functions.py +0 -0
  61. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/comment_tags.lua +0 -0
  62. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/comment_tags_margin.lua +0 -0
  63. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/comment_tags_no_comments.lua +0 -0
  64. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/comment_toggle.js +0 -0
  65. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/comments.css +0 -0
  66. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/continuous.py +0 -0
  67. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/copy_files.py +0 -0
  68. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/diff-doc.js +0 -0
  69. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/doc_contents.py +0 -0
  70. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/flowchart/__init__.py +0 -0
  71. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/flowchart/dot_to_yaml.py +0 -0
  72. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/forward_search.py +0 -0
  73. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/git_gd.py +0 -0
  74. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/git_gd_image.py +0 -0
  75. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/git_gd_qt.py +0 -0
  76. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/html_comments.py +0 -0
  77. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/html_uncomments.py +0 -0
  78. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/log_example.py +0 -0
  79. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/match_spaces.py +0 -0
  80. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/notebook/__init__.py +0 -0
  81. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/notebook/display.py +0 -0
  82. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/notebook/fast_build.py +0 -0
  83. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/notebook/tex_to_qmd.py +0 -0
  84. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/onewordify.py +0 -0
  85. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/onewordify_undo.py +0 -0
  86. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/outline.py +0 -0
  87. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/rearrange_tex.py +0 -0
  88. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/searchacro.py +0 -0
  89. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/separate_comments.py +0 -0
  90. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/split_conflict.py +0 -0
  91. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/unseparate_comments.py +0 -0
  92. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/update_check.py +0 -0
  93. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/wrap_sentences.py +0 -0
  94. {pydifftools-0.1.30 → pydifftools-0.1.32}/pydifftools/xml2xlsx.vbs +0 -0
  95. {pydifftools-0.1.30 → pydifftools-0.1.32}/setup.cfg +0 -0
  96. {pydifftools-0.1.30 → pydifftools-0.1.32}/tests/test_browser_lifecycle.py +0 -0
  97. {pydifftools-0.1.30 → pydifftools-0.1.32}/tests/test_check_single_use.py +0 -0
  98. {pydifftools-0.1.30 → pydifftools-0.1.32}/tests/test_command_line_help.py +0 -0
  99. {pydifftools-0.1.30 → pydifftools-0.1.32}/tests/test_continuous_shutdown.py +0 -0
  100. {pydifftools-0.1.30 → pydifftools-0.1.32}/tests/test_forward_search.py +0 -0
  101. {pydifftools-0.1.30 → pydifftools-0.1.32}/tests/test_git_gd_image.py +0 -0
  102. {pydifftools-0.1.30 → pydifftools-0.1.32}/tests/test_git_gd_qt.py +0 -0
  103. {pydifftools-0.1.30 → pydifftools-0.1.32}/tests/test_rrng.py +0 -0
  104. {pydifftools-0.1.30 → pydifftools-0.1.32}/tests/test_tex_to_qmd.py +0 -0
  105. {pydifftools-0.1.30 → pydifftools-0.1.32}/tests/test_update_check.py +0 -0
  106. {pydifftools-0.1.30 → pydifftools-0.1.32}/tests/test_wrap_check.py +0 -0
  107. {pydifftools-0.1.30 → pydifftools-0.1.32}/tests/test_wrapping.py +0 -0
@@ -8,4 +8,6 @@ include pydifftools/comment_tags.lua
8
8
  include pydifftools/comment_tags_margin.lua
9
9
  include pydifftools/comment_tags_no_comments.lua
10
10
  include pydifftools/comment_toggle.js
11
+ include pydifftools/flowchart/svg-pan-zoom.min.js
12
+ include pydifftools/flowchart/wgrph_view.js
11
13
  recursive-include example_notebook *
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyDiffTools
3
- Version: 0.1.30
3
+ Version: 0.1.32
4
4
  Summary: Diff tools
5
5
  Author: J M Franck
6
6
  License: Copyright (c) 2015, jmfranck
@@ -101,6 +101,15 @@ included are (listed in order of fun/utility):
101
101
  refreshed as you edit the file. This wraps the former
102
102
  ``flowchart/watch_graph.py`` script so all of its functionality is now
103
103
  available through the main ``pydifft`` entry point.
104
+ Add ``--diff-base HEAD`` (or a branch, tag, commit, or quoted reflog
105
+ reference) to compare against that Git snapshot throughout the preview
106
+ session. Additions are green, deletions are red and struck through, and
107
+ unchanged content is muted. Renames share one box; date, status, and
108
+ dependency changes are annotated. Navigation, filters, and reloads retain
109
+ the baseline, whose reference and commit appear below the graph.
110
+ Comparison uses the same repository-relative YAML path; a file absent
111
+ from the revision is treated as an empty plan. Historical file renames
112
+ are not followed, and comparison markup is never written to YAML.
104
113
  - `pydifft tex2qmd file.tex` converts LaTeX sources to Quarto markdown.
105
114
  The converter preserves custom observation blocks and errata tags while
106
115
  translating verbatim/python environments into fenced code blocks so the
@@ -46,6 +46,15 @@ included are (listed in order of fun/utility):
46
46
  refreshed as you edit the file. This wraps the former
47
47
  ``flowchart/watch_graph.py`` script so all of its functionality is now
48
48
  available through the main ``pydifft`` entry point.
49
+ Add ``--diff-base HEAD`` (or a branch, tag, commit, or quoted reflog
50
+ reference) to compare against that Git snapshot throughout the preview
51
+ session. Additions are green, deletions are red and struck through, and
52
+ unchanged content is muted. Renames share one box; date, status, and
53
+ dependency changes are annotated. Navigation, filters, and reloads retain
54
+ the baseline, whose reference and commit appear below the graph.
55
+ Comparison uses the same repository-relative YAML path; a file absent
56
+ from the revision is treated as an empty plan. Historical file renames
57
+ are not followed, and comparison markup is never written to YAML.
49
58
  - `pydifft tex2qmd file.tex` converts LaTeX sources to Quarto markdown.
50
59
  The converter preserves custom observation blocks and errata tags while
51
60
  translating verbatim/python environments into fenced code blocks so the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyDiffTools
3
- Version: 0.1.30
3
+ Version: 0.1.32
4
4
  Summary: Diff tools
5
5
  Author: J M Franck
6
6
  License: Copyright (c) 2015, jmfranck
@@ -101,6 +101,15 @@ included are (listed in order of fun/utility):
101
101
  refreshed as you edit the file. This wraps the former
102
102
  ``flowchart/watch_graph.py`` script so all of its functionality is now
103
103
  available through the main ``pydifft`` entry point.
104
+ Add ``--diff-base HEAD`` (or a branch, tag, commit, or quoted reflog
105
+ reference) to compare against that Git snapshot throughout the preview
106
+ session. Additions are green, deletions are red and struck through, and
107
+ unchanged content is muted. Renames share one box; date, status, and
108
+ dependency changes are annotated. Navigation, filters, and reloads retain
109
+ the baseline, whose reference and commit appear below the graph.
110
+ Comparison uses the same repository-relative YAML path; a file absent
111
+ from the revision is treated as an empty plan. Historical file renames
112
+ are not followed, and comparison markup is never written to YAML.
104
113
  - `pydifft tex2qmd file.tex` converts LaTeX sources to Quarto markdown.
105
114
  The converter preserves custom observation blocks and errata tags while
106
115
  translating verbatim/python environments into fenced code blocks so the
@@ -80,9 +80,12 @@ pydifftools/update_check.py
80
80
  pydifftools/wrap_sentences.py
81
81
  pydifftools/xml2xlsx.vbs
82
82
  pydifftools/flowchart/__init__.py
83
+ pydifftools/flowchart/comparison.py
83
84
  pydifftools/flowchart/dot_to_yaml.py
84
85
  pydifftools/flowchart/graph.py
86
+ pydifftools/flowchart/svg-pan-zoom.min.js
85
87
  pydifftools/flowchart/watch_graph.py
88
+ pydifftools/flowchart/wgrph_view.js
86
89
  pydifftools/notebook/__init__.py
87
90
  pydifftools/notebook/display.py
88
91
  pydifftools/notebook/fast_build.py
@@ -0,0 +1,330 @@
1
+ """An immutable Git snapshot and a disposable plan comparison render model."""
2
+
3
+ from copy import deepcopy
4
+ from difflib import SequenceMatcher
5
+ import html
6
+ from pathlib import Path
7
+ import re
8
+ import subprocess
9
+ import tempfile
10
+
11
+ from .graph import (
12
+ _filter_nodes_for_dot,
13
+ _format_label,
14
+ _normalize_graph_dates,
15
+ _node_text_with_due,
16
+ load_graph_yaml,
17
+ trace_ancestors,
18
+ )
19
+
20
+
21
+ class PlanComparison:
22
+ def __init__(self, path, revision):
23
+ path = Path(path).resolve()
24
+ try:
25
+ root = Path(
26
+ subprocess.check_output(
27
+ [
28
+ "git",
29
+ "-C",
30
+ str(path.parent),
31
+ "rev-parse",
32
+ "--show-toplevel",
33
+ ],
34
+ text=True,
35
+ stderr=subprocess.PIPE,
36
+ ).strip()
37
+ ).resolve()
38
+ relative = path.relative_to(root).as_posix()
39
+ self.commit = subprocess.check_output(
40
+ [
41
+ "git",
42
+ "-C",
43
+ str(root),
44
+ "rev-parse",
45
+ "--verify",
46
+ "--end-of-options",
47
+ f"{revision}^{{commit}}",
48
+ ],
49
+ text=True,
50
+ stderr=subprocess.PIPE,
51
+ ).strip()
52
+ listing = subprocess.check_output(
53
+ [
54
+ "git",
55
+ "-C",
56
+ str(root),
57
+ "ls-tree",
58
+ self.commit,
59
+ "--",
60
+ relative,
61
+ ],
62
+ stderr=subprocess.PIPE,
63
+ )
64
+ self.reference = revision
65
+ self.data = {"nodes": {}}
66
+ if listing:
67
+ content = subprocess.check_output(
68
+ [
69
+ "git",
70
+ "-C",
71
+ str(root),
72
+ "show",
73
+ f"{self.commit}:{relative}",
74
+ ],
75
+ stderr=subprocess.PIPE,
76
+ )
77
+ with tempfile.NamedTemporaryFile(suffix=".yaml") as snapshot:
78
+ snapshot.write(content)
79
+ snapshot.flush()
80
+ self.data = load_graph_yaml(snapshot.name)
81
+ _normalize_graph_dates(self.data)
82
+ except Exception as exc:
83
+ detail = getattr(exc, "stderr", None) or str(exc)
84
+ if isinstance(detail, bytes):
85
+ detail = detail.decode(errors="replace")
86
+ raise ValueError(
87
+ f"Cannot compare {path} against Git revision {revision!r}: "
88
+ f"{detail}"
89
+ ) from exc
90
+
91
+ def correspondence(self, current):
92
+ old = self.data["nodes"]
93
+ new = current["nodes"]
94
+ matches = {name: name for name in old.keys() & new.keys()}
95
+ old_text = {
96
+ k: " ".join(str(v.get("text", "")).split()) for k, v in old.items()
97
+ }
98
+ new_text = {
99
+ k: " ".join(str(v.get("text", "")).split()) for k, v in new.items()
100
+ }
101
+ # {{{ Match unique exact content before conservative edited renames
102
+ remaining_old = set(old) - matches.keys()
103
+ remaining_new = set(new) - set(matches.values())
104
+ for name in sorted(remaining_old):
105
+ candidates = [
106
+ k for k in remaining_new if new_text[k] == old_text[name]
107
+ ]
108
+ peers = [k for k in remaining_old if old_text[k] == old_text[name]]
109
+ if len(candidates) == len(peers) == 1:
110
+ matches[name] = candidates[0]
111
+ remaining_old -= matches.keys()
112
+ remaining_new -= set(matches.values())
113
+ scores = {}
114
+ for a in remaining_old:
115
+ for b in remaining_new:
116
+ scores[a, b] = SequenceMatcher(
117
+ None, old_text[a], new_text[b], autojunk=False
118
+ ).ratio()
119
+ anchors = dict(matches)
120
+ for (a, b), score in sorted(scores.items()):
121
+ if score < 0.9:
122
+ continue
123
+ alternatives = [
124
+ v
125
+ for (x, y), v in scores.items()
126
+ if (x == a or y == b) and (x, y) != (a, b)
127
+ ]
128
+ if alternatives and score - max(alternatives) < 0.1 - 1e-9:
129
+ continue
130
+ agrees = True
131
+ evidence = False
132
+ for relation in ("parents", "children"):
133
+ left = {
134
+ anchors[k]
135
+ for k in old[a].get(relation, [])
136
+ if k in anchors
137
+ }
138
+ right = set(new[b].get(relation, [])) & set(anchors.values())
139
+ evidence |= bool(left or right)
140
+ agrees &= left == right
141
+ if agrees and evidence:
142
+ matches[a] = b
143
+ # }}}
144
+ return matches
145
+
146
+ def label(self, before, after, width):
147
+ """Diff visible words while retaining the current label's markup."""
148
+ deleting = bool(before) and not after
149
+ if deleting:
150
+ after = before
151
+ old = _format_label(before or "", width)[1:-1]
152
+ new = _format_label(after or "", width)[1:-1]
153
+ old_words = re.findall(r"[^\s<>]+", re.sub(r"<[^>]*>", " ", old))
154
+ pieces = re.findall(r"<[^>]*>|\s+|[^\s<]+", new)
155
+ positions = [
156
+ i
157
+ for i, part in enumerate(pieces)
158
+ if not part.startswith("<") and not part.isspace()
159
+ ]
160
+ words = [pieces[i] for i in positions]
161
+ for op, a, b, c, d in SequenceMatcher(
162
+ None, old_words, words, autojunk=False
163
+ ).get_opcodes():
164
+ for index in range(c, d):
165
+ color = "#16803c" if op in ("insert", "replace") else "#888888"
166
+ pos = positions[index]
167
+ if deleting:
168
+ pieces[pos] = "<s>" + pieces[pos] + "</s>"
169
+ color = "#c62828"
170
+ pieces[pos] = f'<font color="{color}">{pieces[pos]}</font>'
171
+ if op in ("delete", "replace"):
172
+ removed = " ".join(old_words[a:b])
173
+ removed = f'<font color="#c62828"><s>{removed}</s></font> '
174
+ if c < len(positions):
175
+ pieces[positions[c]] = removed + pieces[positions[c]]
176
+ else:
177
+ pieces.append(removed)
178
+ return "".join(pieces)
179
+
180
+ def render(self, current, width, target=None, completed=False):
181
+ matches = self.correspondence(current)
182
+ old = self.data["nodes"]
183
+ new = current["nodes"]
184
+ reverse = {b: a for a, b in matches.items()}
185
+ if target is not None:
186
+ candidates = set(old) | set(new)
187
+ if target not in candidates:
188
+ candidates = [
189
+ k for k in candidates if k.lower() == target.lower()
190
+ ]
191
+ if len(candidates) != 1:
192
+ raise ValueError(
193
+ f"Task {target!r} not found or ambiguous."
194
+ )
195
+ target = candidates[0]
196
+ visible = []
197
+ views = []
198
+ for graph, focus in (
199
+ (current, matches.get(target, target)),
200
+ (self.data, reverse.get(target, target)),
201
+ ):
202
+ if target is not None:
203
+ names = [focus] + trace_ancestors(graph, focus)
204
+ view = _filter_nodes_for_dot(graph, names)
205
+ elif completed:
206
+ view = _filter_nodes_for_dot(graph, graph["nodes"], True)
207
+ else:
208
+ view = graph
209
+ visible.append(set(view["nodes"]))
210
+ views.append(view)
211
+ names = visible[0] | {matches.get(k, k) for k in visible[1]}
212
+ model = {"nodes": {}, "styles": deepcopy(current.get("styles", {}))}
213
+ model["styles"] = {
214
+ **deepcopy(self.data.get("styles", {})),
215
+ **model["styles"],
216
+ }
217
+ self.node_states = {}
218
+ self.edge_states = {}
219
+ for name in sorted(names):
220
+ previous = reverse.get(name, name if name in old else None)
221
+ node = deepcopy(new[name] if name in new else old[name])
222
+ before = old.get(previous, {})
223
+ after = new.get(name, {})
224
+ state = "same"
225
+ if name not in new:
226
+ state = "removed"
227
+ elif previous is None:
228
+ state = "added"
229
+ body = self.label(
230
+ before.get("text", ""), after.get("text", ""), width
231
+ )
232
+ if state in ("added", "removed"):
233
+ node["_comparison_status"] = (
234
+ "added" if state == "added" else "deleted"
235
+ )
236
+ if previous is not None and previous != name:
237
+ body = (
238
+ f'<font color="#c62828"><s>{html.escape(previous)}</s>'
239
+ "</font><br/>" + body
240
+ )
241
+ state = "changed"
242
+ # {{{ Explicit metadata changes, including date-only view edges
243
+ for key in ("due", "orig_due", "style", "parents", "children"):
244
+ left = before.get(key, "")
245
+ right = after.get(key, "")
246
+ if key in ("parents", "children"):
247
+ equal = {matches.get(k, k) for k in left} == set(right)
248
+ else:
249
+ equal = left == right
250
+ if equal:
251
+ if key == "due" and right:
252
+ dated = dict(after)
253
+ dated.pop("text", None)
254
+ body += (
255
+ "<br/>"
256
+ + _format_label(_node_text_with_due(dated), width)[
257
+ 1:-1
258
+ ]
259
+ )
260
+ continue
261
+ body += "<br/>" + self.label(
262
+ html.escape(f"{key}: {left or '(none)'}"),
263
+ html.escape(f"{key}: {right or '(none)'}"),
264
+ width,
265
+ )
266
+ # }}}
267
+ if state == "same" and body.count("#16803c") + body.count(
268
+ "#c62828"
269
+ ):
270
+ state = "changed"
271
+ node["_comparison_label"] = body
272
+ node["children"] = []
273
+ node["parents"] = []
274
+ model["nodes"][name] = node
275
+ self.node_states[name] = state
276
+ edges = []
277
+ for graph, mapping in ((self.data, matches), (current, {})):
278
+ edges.append(
279
+ {
280
+ (mapping.get(a, a), mapping.get(b, b))
281
+ for a, node in graph["nodes"].items()
282
+ for b in node.get("children", [])
283
+ }
284
+ )
285
+ visible_edges = {
286
+ (mapping.get(a, a), mapping.get(b, b))
287
+ for graph, mapping in ((views[1], matches), (views[0], {}))
288
+ for a, node in graph["nodes"].items()
289
+ for b in node.get("children", [])
290
+ }
291
+ for a, b in sorted(visible_edges):
292
+ if a not in names or b not in names:
293
+ continue
294
+ model["nodes"][a]["children"].append(b)
295
+ model["nodes"][b]["parents"].append(a)
296
+ self.edge_states[f"{a}->{b}"] = (
297
+ "same"
298
+ if (a, b) in edges[0] & edges[1]
299
+ else "added" if (a, b) in edges[1] else "removed"
300
+ )
301
+ return model
302
+
303
+ def style_svg(self, root, namespace):
304
+ for group in root.iter(f"{namespace}g"):
305
+ kind = group.get("class")
306
+ if kind not in ("node", "edge"):
307
+ continue
308
+ title = group.find(f"{namespace}title")
309
+ states = self.node_states if kind == "node" else self.edge_states
310
+ state = states.get(title.text if title is not None else "", "same")
311
+ color = {"added": "#16803c", "removed": "#c62828"}.get(
312
+ state, "#888888"
313
+ )
314
+ for item in group.iter():
315
+ tag = item.tag.split("}")[-1]
316
+ if tag in ("polygon", "path", "ellipse", "rect"):
317
+ item.set("stroke", color)
318
+ if kind == "node" and item.get("fill") != "none":
319
+ item.set("fill", "#f5f5f5")
320
+ if kind == "edge" and tag == "polygon":
321
+ item.set("fill", color)
322
+ if state == "removed":
323
+ item.set("stroke-dasharray", "5,3")
324
+ elif tag == "text":
325
+ if state in ("added", "removed"):
326
+ item.set("fill", color)
327
+ elif item.get("fill") not in ("#16803c", "#c62828"):
328
+ item.set("fill", "#888888")
329
+ if state == "removed":
330
+ item.set("text-decoration", "line-through")
@@ -42,15 +42,11 @@ def _str_presenter(dumper, data: str):
42
42
  return dumper.represent_scalar("tag:yaml.org,2002:str", data)
43
43
 
44
44
 
45
- def _register_block_str_presenter() -> None:
46
- """Register the multiline string presenter on all dumpers we use."""
47
-
48
- for dumper in (yaml.Dumper, yaml.SafeDumper, IndentDumper):
49
- if getattr(dumper, "yaml_representers", None) is not None:
50
- dumper.add_representer(str, _str_presenter)
51
-
52
-
53
- _register_block_str_presenter()
45
+ # {{{ Register multiline strings with each YAML dumper
46
+ for dumper in (yaml.Dumper, yaml.SafeDumper, IndentDumper):
47
+ if getattr(dumper, "yaml_representers", None) is not None:
48
+ dumper.add_representer(str, _str_presenter)
49
+ # }}}
54
50
 
55
51
 
56
52
  class EmptyGraphYamlError(ValueError):
@@ -428,12 +424,6 @@ def endpoint_projects(data):
428
424
  return projects
429
425
 
430
426
 
431
- def _node_label(text, wrap_width=55):
432
- if text is None:
433
- return ""
434
- return _format_label(text, wrap_width)
435
-
436
-
437
427
  def _normalize_graph_dates(data):
438
428
  # Normalize due dates to mm/dd/yy so the YAML is consistent across years.
439
429
  if "nodes" not in data:
@@ -482,11 +472,16 @@ def _append_node(
482
472
  if parent_due is None or not str(parent_due).strip():
483
473
  depends_on_undated_parent = True
484
474
  break
485
- label = _node_label(
486
- _node_text_with_due(node, depends_on_undated_parent), wrap_width
475
+ label = _format_label(
476
+ _node_text_with_due(node, depends_on_undated_parent) or "", wrap_width
487
477
  )
478
+ if "_comparison_label" in node:
479
+ label = "<" + node["_comparison_label"] + ">"
480
+ status = node.get("_comparison_status")
481
+ status_prefix = f"<b>{status}:</b> " if status else ""
488
482
  task_link_line = (
489
- f'<font point-size="9">__WGRPH_TASK_LINK__:{node_name}</font>'
483
+ f'<font point-size="9">{status_prefix}'
484
+ f"__WGRPH_TASK_LINK__:{node_name}</font>"
490
485
  )
491
486
  if label:
492
487
  label = "<" + task_link_line + '<br align="left"/>' + label[1:]
@@ -734,6 +729,7 @@ def write_dot_from_yaml(
734
729
  filter_task=None,
735
730
  filter_completed=False,
736
731
  resolve_due_date_conflict=None,
732
+ comparison=None,
737
733
  ):
738
734
  data = load_graph_yaml(str(yaml_path), old_data=old_data)
739
735
  _normalize_graph_dates(data)
@@ -847,7 +843,11 @@ def write_dot_from_yaml(
847
843
  adjust_due_after_parents(name)
848
844
  # }}}
849
845
  data_for_dot = data
850
- if filter_task is not None:
846
+ if comparison is not None:
847
+ data_for_dot = comparison.render(
848
+ data, wrap_width, filter_task, filter_completed
849
+ )
850
+ elif filter_task is not None:
851
851
  # Limit the rendered graph to incomplete ancestors of the target task.
852
852
  if "nodes" not in data or filter_task not in data["nodes"]:
853
853
  matches = [
@@ -0,0 +1,30 @@
1
+ /*
2
+ * Vendored unmodified from svg-pan-zoom 3.6.2 (BSD 2-Clause license):
3
+ *
4
+ * Copyright 2009-2010 Andrea Leofreddi <a.leofreddi@vleo.net>
5
+ * All rights reserved.
6
+ *
7
+ * Redistribution and use in source and binary forms, with or without modification,
8
+ * are permitted provided that the following conditions are met:
9
+ *
10
+ * * Redistributions of source code must retain the above copyright notice, this
11
+ * list of conditions and the following disclaimer.
12
+ *
13
+ * * Redistributions in binary form must reproduce the above copyright notice, this
14
+ * list of conditions and the following disclaimer in the documentation and/or
15
+ * other materials provided with the distribution.
16
+ *
17
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
21
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
24
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ */
28
+ // svg-pan-zoom v3.6.2
29
+ // https://github.com/bumbu/svg-pan-zoom
30
+ !function s(r,a,l){function u(e,t){if(!a[e]){if(!r[e]){var o="function"==typeof require&&require;if(!t&&o)return o(e,!0);if(h)return h(e,!0);var n=new Error("Cannot find module '"+e+"'");throw n.code="MODULE_NOT_FOUND",n}var i=a[e]={exports:{}};r[e][0].call(i.exports,function(t){return u(r[e][1][t]||t)},i,i.exports,s,r,a,l)}return a[e].exports}for(var h="function"==typeof require&&require,t=0;t<l.length;t++)u(l[t]);return u}({1:[function(t,e,o){var s=t("./svg-utilities");e.exports={enable:function(t){var e=t.svg.querySelector("defs");if(e||(e=document.createElementNS(s.svgNS,"defs"),t.svg.appendChild(e)),!e.querySelector("style#svg-pan-zoom-controls-styles")){var o=document.createElementNS(s.svgNS,"style");o.setAttribute("id","svg-pan-zoom-controls-styles"),o.setAttribute("type","text/css"),o.textContent=".svg-pan-zoom-control { cursor: pointer; fill: black; fill-opacity: 0.333; } .svg-pan-zoom-control:hover { fill-opacity: 0.8; } .svg-pan-zoom-control-background { fill: white; fill-opacity: 0.5; } .svg-pan-zoom-control-background { fill-opacity: 0.8; }",e.appendChild(o)}var n=document.createElementNS(s.svgNS,"g");n.setAttribute("id","svg-pan-zoom-controls"),n.setAttribute("transform","translate("+(t.width-70)+" "+(t.height-76)+") scale(0.75)"),n.setAttribute("class","svg-pan-zoom-control"),n.appendChild(this._createZoomIn(t)),n.appendChild(this._createZoomReset(t)),n.appendChild(this._createZoomOut(t)),t.svg.appendChild(n),t.controlIcons=n},_createZoomIn:function(t){var e=document.createElementNS(s.svgNS,"g");e.setAttribute("id","svg-pan-zoom-zoom-in"),e.setAttribute("transform","translate(30.5 5) scale(0.015)"),e.setAttribute("class","svg-pan-zoom-control"),e.addEventListener("click",function(){t.getPublicInstance().zoomIn()},!1),e.addEventListener("touchstart",function(){t.getPublicInstance().zoomIn()},!1);var o=document.createElementNS(s.svgNS,"rect");o.setAttribute("x","0"),o.setAttribute("y","0"),o.setAttribute("width","1500"),o.setAttribute("height","1400"),o.setAttribute("class","svg-pan-zoom-control-background"),e.appendChild(o);var n=document.createElementNS(s.svgNS,"path");return n.setAttribute("d","M1280 576v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z"),n.setAttribute("class","svg-pan-zoom-control-element"),e.appendChild(n),e},_createZoomReset:function(t){var e=document.createElementNS(s.svgNS,"g");e.setAttribute("id","svg-pan-zoom-reset-pan-zoom"),e.setAttribute("transform","translate(5 35) scale(0.4)"),e.setAttribute("class","svg-pan-zoom-control"),e.addEventListener("click",function(){t.getPublicInstance().reset()},!1),e.addEventListener("touchstart",function(){t.getPublicInstance().reset()},!1);var o=document.createElementNS(s.svgNS,"rect");o.setAttribute("x","2"),o.setAttribute("y","2"),o.setAttribute("width","182"),o.setAttribute("height","58"),o.setAttribute("class","svg-pan-zoom-control-background"),e.appendChild(o);var n=document.createElementNS(s.svgNS,"path");n.setAttribute("d","M33.051,20.632c-0.742-0.406-1.854-0.609-3.338-0.609h-7.969v9.281h7.769c1.543,0,2.701-0.188,3.473-0.562c1.365-0.656,2.048-1.953,2.048-3.891C35.032,22.757,34.372,21.351,33.051,20.632z"),n.setAttribute("class","svg-pan-zoom-control-element"),e.appendChild(n);var i=document.createElementNS(s.svgNS,"path");return i.setAttribute("d","M170.231,0.5H15.847C7.102,0.5,0.5,5.708,0.5,11.84v38.861C0.5,56.833,7.102,61.5,15.847,61.5h154.384c8.745,0,15.269-4.667,15.269-10.798V11.84C185.5,5.708,178.976,0.5,170.231,0.5z M42.837,48.569h-7.969c-0.219-0.766-0.375-1.383-0.469-1.852c-0.188-0.969-0.289-1.961-0.305-2.977l-0.047-3.211c-0.03-2.203-0.41-3.672-1.142-4.406c-0.732-0.734-2.103-1.102-4.113-1.102h-7.05v13.547h-7.055V14.022h16.524c2.361,0.047,4.178,0.344,5.45,0.891c1.272,0.547,2.351,1.352,3.234,2.414c0.731,0.875,1.31,1.844,1.737,2.906s0.64,2.273,0.64,3.633c0,1.641-0.414,3.254-1.242,4.84s-2.195,2.707-4.102,3.363c1.594,0.641,2.723,1.551,3.387,2.73s0.996,2.98,0.996,5.402v2.32c0,1.578,0.063,2.648,0.19,3.211c0.19,0.891,0.635,1.547,1.333,1.969V48.569z M75.579,48.569h-26.18V14.022h25.336v6.117H56.454v7.336h16.781v6H56.454v8.883h19.125V48.569z M104.497,46.331c-2.44,2.086-5.887,3.129-10.34,3.129c-4.548,0-8.125-1.027-10.731-3.082s-3.909-4.879-3.909-8.473h6.891c0.224,1.578,0.662,2.758,1.316,3.539c1.196,1.422,3.246,2.133,6.15,2.133c1.739,0,3.151-0.188,4.236-0.562c2.058-0.719,3.087-2.055,3.087-4.008c0-1.141-0.504-2.023-1.512-2.648c-1.008-0.609-2.607-1.148-4.796-1.617l-3.74-0.82c-3.676-0.812-6.201-1.695-7.576-2.648c-2.328-1.594-3.492-4.086-3.492-7.477c0-3.094,1.139-5.664,3.417-7.711s5.623-3.07,10.036-3.07c3.685,0,6.829,0.965,9.431,2.895c2.602,1.93,3.966,4.73,4.093,8.402h-6.938c-0.128-2.078-1.057-3.555-2.787-4.43c-1.154-0.578-2.587-0.867-4.301-0.867c-1.907,0-3.428,0.375-4.565,1.125c-1.138,0.75-1.706,1.797-1.706,3.141c0,1.234,0.561,2.156,1.682,2.766c0.721,0.406,2.25,0.883,4.589,1.43l6.063,1.43c2.657,0.625,4.648,1.461,5.975,2.508c2.059,1.625,3.089,3.977,3.089,7.055C108.157,41.624,106.937,44.245,104.497,46.331z M139.61,48.569h-26.18V14.022h25.336v6.117h-18.281v7.336h16.781v6h-16.781v8.883h19.125V48.569z M170.337,20.14h-10.336v28.43h-7.266V20.14h-10.383v-6.117h27.984V20.14z"),i.setAttribute("class","svg-pan-zoom-control-element"),e.appendChild(i),e},_createZoomOut:function(t){var e=document.createElementNS(s.svgNS,"g");e.setAttribute("id","svg-pan-zoom-zoom-out"),e.setAttribute("transform","translate(30.5 70) scale(0.015)"),e.setAttribute("class","svg-pan-zoom-control"),e.addEventListener("click",function(){t.getPublicInstance().zoomOut()},!1),e.addEventListener("touchstart",function(){t.getPublicInstance().zoomOut()},!1);var o=document.createElementNS(s.svgNS,"rect");o.setAttribute("x","0"),o.setAttribute("y","0"),o.setAttribute("width","1500"),o.setAttribute("height","1400"),o.setAttribute("class","svg-pan-zoom-control-background"),e.appendChild(o);var n=document.createElementNS(s.svgNS,"path");return n.setAttribute("d","M1280 576v128q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h896q26 0 45 19t19 45zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z"),n.setAttribute("class","svg-pan-zoom-control-element"),e.appendChild(n),e},disable:function(t){t.controlIcons&&(t.controlIcons.parentNode.removeChild(t.controlIcons),t.controlIcons=null)}}},{"./svg-utilities":5}],2:[function(t,e,o){function n(t,e){this.init(t,e)}var i=t("./svg-utilities"),r=t("./utilities");n.prototype.init=function(t,e){this.viewport=t,this.options=e,this.originalState={zoom:1,x:0,y:0},this.activeState={zoom:1,x:0,y:0},this.updateCTMCached=r.proxy(this.updateCTM,this),this.requestAnimationFrame=r.createRequestAnimationFrame(this.options.refreshRate),this.viewBox={x:0,y:0,width:0,height:0},this.cacheViewBox();var o=this.processCTM();this.setCTM(o),this.updateCTM()},n.prototype.cacheViewBox=function(){var t=this.options.svg.getAttribute("viewBox");if(t){var e=t.split(/[\s\,]/).filter(function(t){return t}).map(parseFloat);this.viewBox.x=e[0],this.viewBox.y=e[1],this.viewBox.width=e[2],this.viewBox.height=e[3];var o=Math.min(this.options.width/this.viewBox.width,this.options.height/this.viewBox.height);this.activeState.zoom=o,this.activeState.x=(this.options.width-this.viewBox.width*o)/2,this.activeState.y=(this.options.height-this.viewBox.height*o)/2,this.updateCTMOnNextFrame(),this.options.svg.removeAttribute("viewBox")}else this.simpleViewBoxCache()},n.prototype.simpleViewBoxCache=function(){var t=this.viewport.getBBox();this.viewBox.x=t.x,this.viewBox.y=t.y,this.viewBox.width=t.width,this.viewBox.height=t.height},n.prototype.getViewBox=function(){return r.extend({},this.viewBox)},n.prototype.processCTM=function(){var t,e=this.getCTM();(this.options.fit||this.options.contain)&&(t=this.options.fit?Math.min(this.options.width/this.viewBox.width,this.options.height/this.viewBox.height):Math.max(this.options.width/this.viewBox.width,this.options.height/this.viewBox.height),e.a=t,e.d=t,e.e=-this.viewBox.x*t,e.f=-this.viewBox.y*t);if(this.options.center){var o=.5*(this.options.width-(this.viewBox.width+2*this.viewBox.x)*e.a),n=.5*(this.options.height-(this.viewBox.height+2*this.viewBox.y)*e.a);e.e=o,e.f=n}return this.originalState.zoom=e.a,this.originalState.x=e.e,this.originalState.y=e.f,e},n.prototype.getOriginalState=function(){return r.extend({},this.originalState)},n.prototype.getState=function(){return r.extend({},this.activeState)},n.prototype.getZoom=function(){return this.activeState.zoom},n.prototype.getRelativeZoom=function(){return this.activeState.zoom/this.originalState.zoom},n.prototype.computeRelativeZoom=function(t){return t/this.originalState.zoom},n.prototype.getPan=function(){return{x:this.activeState.x,y:this.activeState.y}},n.prototype.getCTM=function(){var t=this.options.svg.createSVGMatrix();return t.a=this.activeState.zoom,t.b=0,t.c=0,t.d=this.activeState.zoom,t.e=this.activeState.x,t.f=this.activeState.y,t},n.prototype.setCTM=function(t){var e=this.isZoomDifferent(t),o=this.isPanDifferent(t);if(e||o){if(e&&(!1===this.options.beforeZoom(this.getRelativeZoom(),this.computeRelativeZoom(t.a))?(t.a=t.d=this.activeState.zoom,e=!1):(this.updateCache(t),this.options.onZoom(this.getRelativeZoom()))),o){var n=this.options.beforePan(this.getPan(),{x:t.e,y:t.f}),i=!1,s=!1;!1===n?(t.e=this.getPan().x,t.f=this.getPan().y,i=s=!0):r.isObject(n)&&(!1===n.x?(t.e=this.getPan().x,i=!0):r.isNumber(n.x)&&(t.e=n.x),!1===n.y?(t.f=this.getPan().y,s=!0):r.isNumber(n.y)&&(t.f=n.y)),i&&s||!this.isPanDifferent(t)?o=!1:(this.updateCache(t),this.options.onPan(this.getPan()))}(e||o)&&this.updateCTMOnNextFrame()}},n.prototype.isZoomDifferent=function(t){return this.activeState.zoom!==t.a},n.prototype.isPanDifferent=function(t){return this.activeState.x!==t.e||this.activeState.y!==t.f},n.prototype.updateCache=function(t){this.activeState.zoom=t.a,this.activeState.x=t.e,this.activeState.y=t.f},n.prototype.pendingUpdate=!1,n.prototype.updateCTMOnNextFrame=function(){this.pendingUpdate||(this.pendingUpdate=!0,this.requestAnimationFrame.call(window,this.updateCTMCached))},n.prototype.updateCTM=function(){var t=this.getCTM();i.setCTM(this.viewport,t,this.defs),this.pendingUpdate=!1,this.options.onUpdatedCTM&&this.options.onUpdatedCTM(t)},e.exports=function(t,e){return new n(t,e)}},{"./svg-utilities":5,"./utilities":7}],3:[function(t,e,o){var n,i=t("./svg-pan-zoom.js");n=window,document,"function"==typeof define&&define.amd?define("svg-pan-zoom",function(){return i}):void 0!==e&&e.exports&&(e.exports=i,n.svgPanZoom=i)},{"./svg-pan-zoom.js":4}],4:[function(t,e,o){function i(t,e){this.init(t,e)}var n=t("./uniwheel"),s=t("./control-icons"),r=t("./utilities"),a=t("./svg-utilities"),l=t("./shadow-viewport"),u={viewportSelector:".svg-pan-zoom_viewport",panEnabled:!0,controlIconsEnabled:!1,zoomEnabled:!0,dblClickZoomEnabled:!0,mouseWheelZoomEnabled:!0,preventMouseEventsDefault:!0,zoomScaleSensitivity:.1,minZoom:.5,maxZoom:10,fit:!0,contain:!1,center:!0,refreshRate:"auto",beforeZoom:null,onZoom:null,beforePan:null,onPan:null,customEventsHandler:null,eventsListenerElement:null,onUpdatedCTM:null},h={passive:!0};i.prototype.init=function(t,e){var o=this;this.svg=t,this.defs=t.querySelector("defs"),a.setupSvgAttributes(this.svg),this.options=r.extend(r.extend({},u),e),this.state="none";var n=a.getBoundingClientRectNormalized(t);this.width=n.width,this.height=n.height,this.viewport=l(a.getOrCreateViewport(this.svg,this.options.viewportSelector),{svg:this.svg,width:this.width,height:this.height,fit:this.options.fit,contain:this.options.contain,center:this.options.center,refreshRate:this.options.refreshRate,beforeZoom:function(t,e){if(o.viewport&&o.options.beforeZoom)return o.options.beforeZoom(t,e)},onZoom:function(t){if(o.viewport&&o.options.onZoom)return o.options.onZoom(t)},beforePan:function(t,e){if(o.viewport&&o.options.beforePan)return o.options.beforePan(t,e)},onPan:function(t){if(o.viewport&&o.options.onPan)return o.options.onPan(t)},onUpdatedCTM:function(t){if(o.viewport&&o.options.onUpdatedCTM)return o.options.onUpdatedCTM(t)}});var i=this.getPublicInstance();i.setBeforeZoom(this.options.beforeZoom),i.setOnZoom(this.options.onZoom),i.setBeforePan(this.options.beforePan),i.setOnPan(this.options.onPan),i.setOnUpdatedCTM(this.options.onUpdatedCTM),this.options.controlIconsEnabled&&s.enable(this),this.lastMouseWheelEventTime=Date.now(),this.setupHandlers()},i.prototype.setupHandlers=function(){var o=this,n=null;if(this.eventListeners={mousedown:function(t){var e=o.handleMouseDown(t,n);return n=t,e},touchstart:function(t){var e=o.handleMouseDown(t,n);return n=t,e},mouseup:function(t){return o.handleMouseUp(t)},touchend:function(t){return o.handleMouseUp(t)},mousemove:function(t){return o.handleMouseMove(t)},touchmove:function(t){return o.handleMouseMove(t)},mouseleave:function(t){return o.handleMouseUp(t)},touchleave:function(t){return o.handleMouseUp(t)},touchcancel:function(t){return o.handleMouseUp(t)}},null!=this.options.customEventsHandler){this.options.customEventsHandler.init({svgElement:this.svg,eventsListenerElement:this.options.eventsListenerElement,instance:this.getPublicInstance()});var t=this.options.customEventsHandler.haltEventListeners;if(t&&t.length)for(var e=t.length-1;0<=e;e--)this.eventListeners.hasOwnProperty(t[e])&&delete this.eventListeners[t[e]]}for(var i in this.eventListeners)(this.options.eventsListenerElement||this.svg).addEventListener(i,this.eventListeners[i],!this.options.preventMouseEventsDefault&&h);this.options.mouseWheelZoomEnabled&&(this.options.mouseWheelZoomEnabled=!1,this.enableMouseWheelZoom())},i.prototype.enableMouseWheelZoom=function(){if(!this.options.mouseWheelZoomEnabled){var e=this;this.wheelListener=function(t){return e.handleMouseWheel(t)};var t=!this.options.preventMouseEventsDefault;n.on(this.options.eventsListenerElement||this.svg,this.wheelListener,t),this.options.mouseWheelZoomEnabled=!0}},i.prototype.disableMouseWheelZoom=function(){if(this.options.mouseWheelZoomEnabled){var t=!this.options.preventMouseEventsDefault;n.off(this.options.eventsListenerElement||this.svg,this.wheelListener,t),this.options.mouseWheelZoomEnabled=!1}},i.prototype.handleMouseWheel=function(t){if(this.options.zoomEnabled&&"none"===this.state){this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1);var e=t.deltaY||1,o=Date.now()-this.lastMouseWheelEventTime,n=3+Math.max(0,30-o);this.lastMouseWheelEventTime=Date.now(),"deltaMode"in t&&0===t.deltaMode&&t.wheelDelta&&(e=0===t.deltaY?0:Math.abs(t.wheelDelta)/t.deltaY),e=-.3<e&&e<.3?e:(0<e?1:-1)*Math.log(Math.abs(e)+10)/n;var i=this.svg.getScreenCTM().inverse(),s=a.getEventPoint(t,this.svg).matrixTransform(i),r=Math.pow(1+this.options.zoomScaleSensitivity,-1*e);this.zoomAtPoint(r,s)}},i.prototype.zoomAtPoint=function(t,e,o){var n=this.viewport.getOriginalState();o?(t=Math.max(this.options.minZoom*n.zoom,Math.min(this.options.maxZoom*n.zoom,t)),t/=this.getZoom()):this.getZoom()*t<this.options.minZoom*n.zoom?t=this.options.minZoom*n.zoom/this.getZoom():this.getZoom()*t>this.options.maxZoom*n.zoom&&(t=this.options.maxZoom*n.zoom/this.getZoom());var i=this.viewport.getCTM(),s=e.matrixTransform(i.inverse()),r=this.svg.createSVGMatrix().translate(s.x,s.y).scale(t).translate(-s.x,-s.y),a=i.multiply(r);a.a!==i.a&&this.viewport.setCTM(a)},i.prototype.zoom=function(t,e){this.zoomAtPoint(t,a.getSvgCenterPoint(this.svg,this.width,this.height),e)},i.prototype.publicZoom=function(t,e){e&&(t=this.computeFromRelativeZoom(t)),this.zoom(t,e)},i.prototype.publicZoomAtPoint=function(t,e,o){if(o&&(t=this.computeFromRelativeZoom(t)),"SVGPoint"!==r.getType(e)){if(!("x"in e&&"y"in e))throw new Error("Given point is invalid");e=a.createSVGPoint(this.svg,e.x,e.y)}this.zoomAtPoint(t,e,o)},i.prototype.getZoom=function(){return this.viewport.getZoom()},i.prototype.getRelativeZoom=function(){return this.viewport.getRelativeZoom()},i.prototype.computeFromRelativeZoom=function(t){return t*this.viewport.getOriginalState().zoom},i.prototype.resetZoom=function(){var t=this.viewport.getOriginalState();this.zoom(t.zoom,!0)},i.prototype.resetPan=function(){this.pan(this.viewport.getOriginalState())},i.prototype.reset=function(){this.resetZoom(),this.resetPan()},i.prototype.handleDblClick=function(t){var e;if((this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),this.options.controlIconsEnabled)&&-1<(t.target.getAttribute("class")||"").indexOf("svg-pan-zoom-control"))return!1;e=t.shiftKey?1/(2*(1+this.options.zoomScaleSensitivity)):2*(1+this.options.zoomScaleSensitivity);var o=a.getEventPoint(t,this.svg).matrixTransform(this.svg.getScreenCTM().inverse());this.zoomAtPoint(e,o)},i.prototype.handleMouseDown=function(t,e){this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),r.mouseAndTouchNormalize(t,this.svg),this.options.dblClickZoomEnabled&&r.isDblClick(t,e)?this.handleDblClick(t):(this.state="pan",this.firstEventCTM=this.viewport.getCTM(),this.stateOrigin=a.getEventPoint(t,this.svg).matrixTransform(this.firstEventCTM.inverse()))},i.prototype.handleMouseMove=function(t){if(this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),"pan"===this.state&&this.options.panEnabled){var e=a.getEventPoint(t,this.svg).matrixTransform(this.firstEventCTM.inverse()),o=this.firstEventCTM.translate(e.x-this.stateOrigin.x,e.y-this.stateOrigin.y);this.viewport.setCTM(o)}},i.prototype.handleMouseUp=function(t){this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),"pan"===this.state&&(this.state="none")},i.prototype.fit=function(){var t=this.viewport.getViewBox(),e=Math.min(this.width/t.width,this.height/t.height);this.zoom(e,!0)},i.prototype.contain=function(){var t=this.viewport.getViewBox(),e=Math.max(this.width/t.width,this.height/t.height);this.zoom(e,!0)},i.prototype.center=function(){var t=this.viewport.getViewBox(),e=.5*(this.width-(t.width+2*t.x)*this.getZoom()),o=.5*(this.height-(t.height+2*t.y)*this.getZoom());this.getPublicInstance().pan({x:e,y:o})},i.prototype.updateBBox=function(){this.viewport.simpleViewBoxCache()},i.prototype.pan=function(t){var e=this.viewport.getCTM();e.e=t.x,e.f=t.y,this.viewport.setCTM(e)},i.prototype.panBy=function(t){var e=this.viewport.getCTM();e.e+=t.x,e.f+=t.y,this.viewport.setCTM(e)},i.prototype.getPan=function(){var t=this.viewport.getState();return{x:t.x,y:t.y}},i.prototype.resize=function(){var t=a.getBoundingClientRectNormalized(this.svg);this.width=t.width,this.height=t.height;var e=this.viewport;e.options.width=this.width,e.options.height=this.height,e.processCTM(),this.options.controlIconsEnabled&&(this.getPublicInstance().disableControlIcons(),this.getPublicInstance().enableControlIcons())},i.prototype.destroy=function(){var e=this;for(var t in this.beforeZoom=null,this.onZoom=null,this.beforePan=null,this.onPan=null,(this.onUpdatedCTM=null)!=this.options.customEventsHandler&&this.options.customEventsHandler.destroy({svgElement:this.svg,eventsListenerElement:this.options.eventsListenerElement,instance:this.getPublicInstance()}),this.eventListeners)(this.options.eventsListenerElement||this.svg).removeEventListener(t,this.eventListeners[t],!this.options.preventMouseEventsDefault&&h);this.disableMouseWheelZoom(),this.getPublicInstance().disableControlIcons(),this.reset(),c=c.filter(function(t){return t.svg!==e.svg}),delete this.options,delete this.viewport,delete this.publicInstance,delete this.pi,this.getPublicInstance=function(){return null}},i.prototype.getPublicInstance=function(){var o=this;return this.publicInstance||(this.publicInstance=this.pi={enablePan:function(){return o.options.panEnabled=!0,o.pi},disablePan:function(){return o.options.panEnabled=!1,o.pi},isPanEnabled:function(){return!!o.options.panEnabled},pan:function(t){return o.pan(t),o.pi},panBy:function(t){return o.panBy(t),o.pi},getPan:function(){return o.getPan()},setBeforePan:function(t){return o.options.beforePan=null===t?null:r.proxy(t,o.publicInstance),o.pi},setOnPan:function(t){return o.options.onPan=null===t?null:r.proxy(t,o.publicInstance),o.pi},enableZoom:function(){return o.options.zoomEnabled=!0,o.pi},disableZoom:function(){return o.options.zoomEnabled=!1,o.pi},isZoomEnabled:function(){return!!o.options.zoomEnabled},enableControlIcons:function(){return o.options.controlIconsEnabled||(o.options.controlIconsEnabled=!0,s.enable(o)),o.pi},disableControlIcons:function(){return o.options.controlIconsEnabled&&(o.options.controlIconsEnabled=!1,s.disable(o)),o.pi},isControlIconsEnabled:function(){return!!o.options.controlIconsEnabled},enableDblClickZoom:function(){return o.options.dblClickZoomEnabled=!0,o.pi},disableDblClickZoom:function(){return o.options.dblClickZoomEnabled=!1,o.pi},isDblClickZoomEnabled:function(){return!!o.options.dblClickZoomEnabled},enableMouseWheelZoom:function(){return o.enableMouseWheelZoom(),o.pi},disableMouseWheelZoom:function(){return o.disableMouseWheelZoom(),o.pi},isMouseWheelZoomEnabled:function(){return!!o.options.mouseWheelZoomEnabled},setZoomScaleSensitivity:function(t){return o.options.zoomScaleSensitivity=t,o.pi},setMinZoom:function(t){return o.options.minZoom=t,o.pi},setMaxZoom:function(t){return o.options.maxZoom=t,o.pi},setBeforeZoom:function(t){return o.options.beforeZoom=null===t?null:r.proxy(t,o.publicInstance),o.pi},setOnZoom:function(t){return o.options.onZoom=null===t?null:r.proxy(t,o.publicInstance),o.pi},zoom:function(t){return o.publicZoom(t,!0),o.pi},zoomBy:function(t){return o.publicZoom(t,!1),o.pi},zoomAtPoint:function(t,e){return o.publicZoomAtPoint(t,e,!0),o.pi},zoomAtPointBy:function(t,e){return o.publicZoomAtPoint(t,e,!1),o.pi},zoomIn:function(){return this.zoomBy(1+o.options.zoomScaleSensitivity),o.pi},zoomOut:function(){return this.zoomBy(1/(1+o.options.zoomScaleSensitivity)),o.pi},getZoom:function(){return o.getRelativeZoom()},setOnUpdatedCTM:function(t){return o.options.onUpdatedCTM=null===t?null:r.proxy(t,o.publicInstance),o.pi},resetZoom:function(){return o.resetZoom(),o.pi},resetPan:function(){return o.resetPan(),o.pi},reset:function(){return o.reset(),o.pi},fit:function(){return o.fit(),o.pi},contain:function(){return o.contain(),o.pi},center:function(){return o.center(),o.pi},updateBBox:function(){return o.updateBBox(),o.pi},resize:function(){return o.resize(),o.pi},getSizes:function(){return{width:o.width,height:o.height,realZoom:o.getZoom(),viewBox:o.viewport.getViewBox()}},destroy:function(){return o.destroy(),o.pi}}),this.publicInstance};var c=[];e.exports=function(t,e){var o=r.getSvg(t);if(null===o)return null;for(var n=c.length-1;0<=n;n--)if(c[n].svg===o)return c[n].instance.getPublicInstance();return c.push({svg:o,instance:new i(o,e)}),c[c.length-1].instance.getPublicInstance()}},{"./control-icons":1,"./shadow-viewport":2,"./svg-utilities":5,"./uniwheel":6,"./utilities":7}],5:[function(t,e,o){var l=t("./utilities"),s="unknown";document.documentMode&&(s="ie"),e.exports={svgNS:"http://www.w3.org/2000/svg",xmlNS:"http://www.w3.org/XML/1998/namespace",xmlnsNS:"http://www.w3.org/2000/xmlns/",xlinkNS:"http://www.w3.org/1999/xlink",evNS:"http://www.w3.org/2001/xml-events",getBoundingClientRectNormalized:function(t){if(t.clientWidth&&t.clientHeight)return{width:t.clientWidth,height:t.clientHeight};if(t.getBoundingClientRect())return t.getBoundingClientRect();throw new Error("Cannot get BoundingClientRect for SVG.")},getOrCreateViewport:function(t,e){var o=null;if(!(o=l.isElement(e)?e:t.querySelector(e))){var n=Array.prototype.slice.call(t.childNodes||t.children).filter(function(t){return"defs"!==t.nodeName&&"#text"!==t.nodeName});1===n.length&&"g"===n[0].nodeName&&null===n[0].getAttribute("transform")&&(o=n[0])}if(!o){var i="viewport-"+(new Date).toISOString().replace(/\D/g,"");(o=document.createElementNS(this.svgNS,"g")).setAttribute("id",i);var s=t.childNodes||t.children;if(s&&0<s.length)for(var r=s.length;0<r;r--)"defs"!==s[s.length-r].nodeName&&o.appendChild(s[s.length-r]);t.appendChild(o)}var a=[];return o.getAttribute("class")&&(a=o.getAttribute("class").split(" ")),~a.indexOf("svg-pan-zoom_viewport")||(a.push("svg-pan-zoom_viewport"),o.setAttribute("class",a.join(" "))),o},setupSvgAttributes:function(t){if(t.setAttribute("xmlns",this.svgNS),t.setAttributeNS(this.xmlnsNS,"xmlns:xlink",this.xlinkNS),t.setAttributeNS(this.xmlnsNS,"xmlns:ev",this.evNS),null!==t.parentNode){var e=t.getAttribute("style")||"";-1===e.toLowerCase().indexOf("overflow")&&t.setAttribute("style","overflow: hidden; "+e)}},internetExplorerRedisplayInterval:300,refreshDefsGlobal:l.throttle(function(){for(var t=document.querySelectorAll("defs"),e=t.length,o=0;o<e;o++){var n=t[o];n.parentNode.insertBefore(n,n)}},this?this.internetExplorerRedisplayInterval:null),setCTM:function(t,e,o){var n=this,i="matrix("+e.a+","+e.b+","+e.c+","+e.d+","+e.e+","+e.f+")";t.setAttributeNS(null,"transform",i),"transform"in t.style?t.style.transform=i:"-ms-transform"in t.style?t.style["-ms-transform"]=i:"-webkit-transform"in t.style&&(t.style["-webkit-transform"]=i),"ie"===s&&o&&(o.parentNode.insertBefore(o,o),window.setTimeout(function(){n.refreshDefsGlobal()},n.internetExplorerRedisplayInterval))},getEventPoint:function(t,e){var o=e.createSVGPoint();return l.mouseAndTouchNormalize(t,e),o.x=t.clientX,o.y=t.clientY,o},getSvgCenterPoint:function(t,e,o){return this.createSVGPoint(t,e/2,o/2)},createSVGPoint:function(t,e,o){var n=t.createSVGPoint();return n.x=e,n.y=o,n}}},{"./utilities":7}],6:[function(t,e,o){function s(t,o){function e(t){var e={originalEvent:t=t||window.event,target:t.target||t.srcElement,type:"wheel",deltaMode:"MozMousePixelScroll"==t.type?0:1,deltaX:0,delatZ:0,preventDefault:function(){t.preventDefault?t.preventDefault():t.returnValue=!1}};return"mousewheel"==l?(e.deltaY=-.025*t.wheelDelta,t.wheelDeltaX&&(e.deltaX=-.025*t.wheelDeltaX)):e.deltaY=t.detail,o(e)}return h.push({element:t,fn:e}),e}function n(t,e,o,n){var i;i="wheel"===l?o:s(t,o),t[r](u+e,i,n?c:p)}function i(t,e,o,n){var i;i="wheel"===l?o:function(t){for(var e=0;e<h.length;e++)if(h[e].element===t)return h[e].fn;return function(){}}(t),t[a](u+e,i,n?c:p),function(t){for(var e=0;e<h.length;e++)if(h[e].element===t)return h.splice(e,1)}(t)}var r,a,l,u,h,c,p;e.exports=(h=[],p={passive:!(c={passive:!(u="")})},window.addEventListener?(r="addEventListener",a="removeEventListener"):(r="attachEvent",a="detachEvent",u="on"),l="onwheel"in document.createElement("div")?"wheel":void 0!==document.onmousewheel?"mousewheel":"DOMMouseScroll",{on:function(t,e,o){n(t,l,e,o),"DOMMouseScroll"==l&&n(t,"MozMousePixelScroll",e,o)},off:function(t,e,o){i(t,l,e,o),"DOMMouseScroll"==l&&i(t,"MozMousePixelScroll",e,o)}})},{}],7:[function(t,e,o){function n(e){return function(t){window.setTimeout(t,e)}}e.exports={extend:function(t,e){for(var o in t=t||{},e)this.isObject(e[o])?t[o]=this.extend(t[o],e[o]):t[o]=e[o];return t},isElement:function(t){return t instanceof HTMLElement||t instanceof SVGElement||t instanceof SVGSVGElement||t&&"object"==typeof t&&null!==t&&1===t.nodeType&&"string"==typeof t.nodeName},isObject:function(t){return"[object Object]"===Object.prototype.toString.call(t)},isNumber:function(t){return!isNaN(parseFloat(t))&&isFinite(t)},getSvg:function(t){var e,o;if(this.isElement(t))e=t;else{if(!("string"==typeof t||t instanceof String))throw new Error("Provided selector is not an HTML object nor String");if(!(e=document.querySelector(t)))throw new Error("Provided selector did not find any elements. Selector: "+t)}if("svg"===e.tagName.toLowerCase())o=e;else if("object"===e.tagName.toLowerCase())o=e.contentDocument.documentElement;else{if("embed"!==e.tagName.toLowerCase())throw"img"===e.tagName.toLowerCase()?new Error('Cannot script an SVG in an "img" element. Please use an "object" element or an in-line SVG.'):new Error("Cannot get SVG.");o=e.getSVGDocument().documentElement}return o},proxy:function(t,e){return function(){return t.apply(e,arguments)}},getType:function(t){return Object.prototype.toString.apply(t).replace(/^\[object\s/,"").replace(/\]$/,"")},mouseAndTouchNormalize:function(t,e){if(void 0===t.clientX||null===t.clientX)if(t.clientX=0,void(t.clientY=0)!==t.touches&&t.touches.length){if(void 0!==t.touches[0].clientX)t.clientX=t.touches[0].clientX,t.clientY=t.touches[0].clientY;else if(void 0!==t.touches[0].pageX){var o=e.getBoundingClientRect();t.clientX=t.touches[0].pageX-o.left,t.clientY=t.touches[0].pageY-o.top}}else void 0!==t.originalEvent&&void 0!==t.originalEvent.clientX&&(t.clientX=t.originalEvent.clientX,t.clientY=t.originalEvent.clientY)},isDblClick:function(t,e){if(2===t.detail)return!0;if(null==e)return!1;var o=t.timeStamp-e.timeStamp,n=Math.sqrt(Math.pow(t.clientX-e.clientX,2)+Math.pow(t.clientY-e.clientY,2));return o<250&&n<10},now:Date.now||function(){return(new Date).getTime()},throttle:function(o,n,i){var s,r,a,l=this,u=null,h=0;i=i||{};function c(){h=!1===i.leading?0:l.now(),u=null,a=o.apply(s,r),u||(s=r=null)}return function(){var t=l.now();h||!1!==i.leading||(h=t);var e=n-(t-h);return s=this,r=arguments,e<=0||n<e?(clearTimeout(u),u=null,h=t,a=o.apply(s,r),u||(s=r=null)):u||!1===i.trailing||(u=setTimeout(c,e)),a}},createRequestAnimationFrame:function(t){var e=null;return"auto"!==t&&t<60&&1<t&&(e=Math.floor(1e3/t)),null===e?window.requestAnimationFrame||n(33):n(e)}}},{}]},{},[3]);