dtree-explorer 1.0.0__tar.gz → 1.0.2__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.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dtree-explorer
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: DTree Explorer: encode a directed tree with DTree's two projections (Q1, Q2) and watch it step by step, in the browser
5
5
  Author-email: Enis Olgac <enis@digraphs.info>
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/EnisOlgac/DTree-Explorer
8
8
  Project-URL: Library, https://github.com/EnisOlgac/DTree
9
- Project-URL: Online, https://digraphs.blog/dtree-demo/
9
+ Project-URL: Online, https://dtree.digraphs.net/
10
10
  Project-URL: Paper, https://doi.org/10.5772/intechopen.1013408
11
11
  Keywords: directed tree,tree topology,reachability,visualization,incremental encoding,teaching
12
12
  Classifier: Development Status :: 4 - Beta
@@ -32,7 +32,7 @@ Every tree is computed by the published library, [dtree-encoding](https://pypi.o
32
32
 
33
33
  ## Three ways to use it
34
34
 
35
- **Online:** [digraphs.blog/dtree-demo](https://digraphs.blog/dtree-demo/), nothing to install.
35
+ **Online:** [dtree.digraphs.net](https://dtree.digraphs.net/), nothing to install.
36
36
 
37
37
  **As a Python program**, offline, with the library installed on your machine:
38
38
 
@@ -59,11 +59,11 @@ The examples include the trees of the paper: the introductory example, Figure 1,
59
59
 
60
60
  ## Privacy
61
61
 
62
- Your tree stays on your computer. The page counts visits only in the copy on digraphs.blog, with Google Analytics and only after you agree; the package and the release file count nothing.
62
+ Your tree stays on your computer. Only the online copy at dtree.digraphs.net counts visits, with [GoatCounter](https://www.goatcounter.com/): no cookies, no personal data. The package and the release file count nothing.
63
63
 
64
64
  ## Building
65
65
 
66
- Working in this repository needs the library installed: `pip install dtree-encoding`. `src/template.html` is the page, `dtree_explorer/bridge.py` the Python part it runs, `src/build.py` assembles `dtree_explorer/explorer.html` (package and release) and `site/index.html` (digraphs.blog) with the installed dtree-encoding. Tests: `python -m unittest discover -s tests`.
66
+ Working in this repository needs the library installed: `pip install dtree-encoding`. `src/template.html` is the page, `dtree_explorer/bridge.py` the Python part it runs, `src/build.py` assembles `dtree_explorer/explorer.html` (package and release) and `site/index.html` (dtree.digraphs.net) with the installed dtree-encoding. Tests: `python -m unittest discover -s tests`.
67
67
 
68
68
  ## Background
69
69
 
@@ -6,7 +6,7 @@ Every tree is computed by the published library, [dtree-encoding](https://pypi.o
6
6
 
7
7
  ## Three ways to use it
8
8
 
9
- **Online:** [digraphs.blog/dtree-demo](https://digraphs.blog/dtree-demo/), nothing to install.
9
+ **Online:** [dtree.digraphs.net](https://dtree.digraphs.net/), nothing to install.
10
10
 
11
11
  **As a Python program**, offline, with the library installed on your machine:
12
12
 
@@ -33,11 +33,11 @@ The examples include the trees of the paper: the introductory example, Figure 1,
33
33
 
34
34
  ## Privacy
35
35
 
36
- Your tree stays on your computer. The page counts visits only in the copy on digraphs.blog, with Google Analytics and only after you agree; the package and the release file count nothing.
36
+ Your tree stays on your computer. Only the online copy at dtree.digraphs.net counts visits, with [GoatCounter](https://www.goatcounter.com/): no cookies, no personal data. The package and the release file count nothing.
37
37
 
38
38
  ## Building
39
39
 
40
- Working in this repository needs the library installed: `pip install dtree-encoding`. `src/template.html` is the page, `dtree_explorer/bridge.py` the Python part it runs, `src/build.py` assembles `dtree_explorer/explorer.html` (package and release) and `site/index.html` (digraphs.blog) with the installed dtree-encoding. Tests: `python -m unittest discover -s tests`.
40
+ Working in this repository needs the library installed: `pip install dtree-encoding`. `src/template.html` is the page, `dtree_explorer/bridge.py` the Python part it runs, `src/build.py` assembles `dtree_explorer/explorer.html` (package and release) and `site/index.html` (dtree.digraphs.net) with the installed dtree-encoding. Tests: `python -m unittest discover -s tests`.
41
41
 
42
42
  ## Background
43
43
 
@@ -1,3 +1,3 @@
1
1
  """DTree Explorer: encode a directed tree with DTree's projections Q1 and Q2 and watch it, step by step."""
2
2
 
3
- __version__ = "1.0.0"
3
+ __version__ = "1.0.2"
@@ -64,6 +64,11 @@ button:focus-visible, select:focus-visible, textarea:focus-visible, input:focus-
64
64
  .toolbar { display: flex; flex-wrap: wrap; gap: 10px 18px; align-items: center; }
65
65
  .stepper { display: flex; align-items: center; gap: 8px; flex: 1 1 320px; min-width: 0; }
66
66
  .stepper input[type="range"] { flex: 1; min-width: 80px; accent-color: var(--accent); }
67
+ .stepper button { white-space: nowrap; }
68
+ @media (max-width: 560px) {
69
+ .stepper { flex-wrap: wrap; }
70
+ .stepper input[type="range"] { order: -1; flex-basis: 100%; } /* slider on its own row above the buttons */
71
+ }
67
72
  .steplabel { font-size: 14px; min-height: 1.5em; }
68
73
  .steplabel b { font-family: var(--mono); }
69
74
  .chips { display: flex; flex-wrap: wrap; gap: 6px; }
@@ -100,9 +105,6 @@ footer.band { background: var(--peach); margin-top: 8px; }
100
105
  footer.band .wrap { padding-block: 16px 22px; font-size: 13px; display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px 24px; }
101
106
  footer.band p { margin: 2px 0; }
102
107
  footer.band a { color: var(--ink); }
103
- #consent { position: fixed; left: 0; right: 0; bottom: 0; z-index: 20; background: var(--paper); border-top: 2px solid var(--peach-deep); box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.08); padding-bottom: env(safe-area-inset-bottom, 0px); }
104
- #consent .wrap { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 18px; padding-block: 12px; font-size: 14px; }
105
- #consent .row button.agree { background: #2e9b4f; border-color: #2e9b4f; color: #fff; font-weight: 600; }
106
108
  .sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
107
109
  @media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
108
110
  </style>
@@ -151,9 +153,9 @@ footer.band a { color: var(--ink); }
151
153
  <div class="card">
152
154
  <div class="toolbar">
153
155
  <div class="stepper">
154
- <button id="prev" type="button" aria-label="Previous step">&#9664;</button>
156
+ <button id="prev" type="button" title="Previous edge (left arrow key)">&#9664; Previous edge</button>
155
157
  <input id="step" type="range" min="0" max="0" value="0" aria-label="Step">
156
- <button id="next" type="button" aria-label="Next step">&#9654;</button>
158
+ <button id="next" type="button" title="Next edge (right arrow key)">Next edge &#9654;</button>
157
159
  <button id="play" type="button">Play</button>
158
160
  </div>
159
161
  <div class="chips" role="group" aria-label="Views">
@@ -207,19 +209,13 @@ footer.band a { color: var(--ink); }
207
209
  <footer class="band">
208
210
  <div class="wrap">
209
211
  <div>
210
- <p><b>DTree Explorer 1.0.0</b> &middot; computed by <a href="https://pypi.org/project/dtree-encoding/">dtree-encoding</a> (MIT) &middot; <a href="https://github.com/EnisOlgac/DTree-Explorer">source on GitHub</a> &middot; <a href="https://doi.org/10.5772/intechopen.1013408">the paper</a></p>
212
+ <p><b>DTree Explorer 1.0.2</b> &middot; computed by <a href="https://pypi.org/project/dtree-encoding/">dtree-encoding</a> (MIT) &middot; <a href="https://github.com/EnisOlgac/DTree-Explorer">source on GitHub</a> &middot; <a href="https://doi.org/10.5772/intechopen.1013408">the paper</a></p>
211
213
  <p>Your tree never leaves your computer. Opened from a file or a website, the page loads the Python runtime from cdn.jsdelivr.net and the package from pypi.org; started with <code>dtree-explorer</code>, it uses the library installed on your machine.</p>
212
214
  </div>
213
- <div><p>Contact: Enis Olgac, enis@digraphs.info</p><p>Copyright &copy; 2026 by Ihsan Enis Olgac, B&ouml;blingen</p></div>
215
+ <div><p>Contact: Enis Olgac, enis@digraphs.info</p><p>Copyright &copy; 2026 by Ihsan Enis Olgac, B&ouml;blingen</p><p id="legal" hidden><a href="imprint.html">Imprint</a> &middot; <a id="privacy-link" href="#">Privacy</a></p></div>
214
216
  </div>
215
217
  </footer>
216
218
 
217
- <div id="consent" hidden>
218
- <div class="wrap">
219
- <span>This page counts visits with Google Analytics, using cookies, if you agree. Please read the statement on <a id="privacy-link" href="https://digraphs.blog/">data protection and privacy</a>.</span>
220
- <span class="row"><button type="button" id="c-no">No thanks</button><button type="button" class="agree" id="c-yes">I agree</button></span>
221
- </div>
222
- </div>
223
219
 
224
220
  <script type="text/plain" id="src-init">"""DTree: directed tree library with dual projections Q1 and Q2."""
225
221
 
@@ -1472,9 +1468,9 @@ def encode(text, limit=LIMIT):
1472
1468
  const CONFIG = {
1473
1469
  dtreeVersion: "1.8.0",
1474
1470
  pyodideURL: "https://cdn.jsdelivr.net/pyodide/v0.26.4/full/",
1475
- explorerVersion: "1.0.0",
1476
- gaId: "", // Google Analytics 4, only in the copy for digraphs.blog; empty elsewhere
1477
- privacyURL: "", // statement on data protection and privacy (blog copy)
1471
+ explorerVersion: "1.0.2",
1472
+ counter: "", // GoatCounter endpoint, only in the online copy (dtree.digraphs.net); empty elsewhere
1473
+ privacyURL: "", // privacy notice of the online copy; empty elsewhere
1478
1474
  };
1479
1475
  const EXAMPLES = {
1480
1476
  "Quick start": "A: B; D\nB: C\n",
@@ -1707,7 +1703,7 @@ function render() {
1707
1703
  }
1708
1704
  const si = C.stepInfo;
1709
1705
  $("#steplabel").innerHTML = d.stepwise
1710
- ? `Step ${state.step + 1} of ${total}: <b>${esc(si.op)}</b> <span style="color:var(--muted)">(line ${si.line})</span> &middot; ${C.s.q1.length} nodes`
1706
+ ? `Edge ${state.step + 1} of ${total}: <b>${edgeText(si.op)}</b> <span style="color:var(--muted)">(line ${si.line})</span> &middot; ${C.s.q1.length} nodes`
1711
1707
  : `Final tree: ${C.s.q1.length} nodes. Step by step is available up to ${d.limit} insertions.`;
1712
1708
  $("#stats").textContent = `${d.final.q1.length} nodes · renumberings Q1 / Q2: ${d.renumber[0]} / ${d.renumber[1]}`;
1713
1709
  if ($("#v-tree").checked) $("#tree").innerHTML = treeSVG(C);
@@ -1720,6 +1716,14 @@ function render() {
1720
1716
  $("#query").innerHTML = queryHTML(C);
1721
1717
  }
1722
1718
 
1719
+ // "p:c" -> "p → c", "s>c" -> "c after s", "s<c" -> "c before s"
1720
+ function edgeText(op) {
1721
+ const at = op.search(/[:<>]/);
1722
+ const a = esc(op.slice(0, at)), b = esc(op.slice(at + 1)), k = op[at];
1723
+ if (k === ":") return `${a} &rarr; ${b}`;
1724
+ return `${b} ${k === ">" ? "after" : "before"} ${a}`;
1725
+ }
1726
+
1723
1727
  function setData(data) {
1724
1728
  stopPlay();
1725
1729
  state.data = data;
@@ -1790,6 +1794,15 @@ function wire() {
1790
1794
  });
1791
1795
  $("#text").addEventListener("keydown", (ev) => { if ((ev.ctrlKey || ev.metaKey) && ev.key === "Enter") runEncode(); });
1792
1796
  $("#step").addEventListener("input", (ev) => { stopPlay(); goStep(+ev.target.value); });
1797
+ document.addEventListener("keydown", (ev) => { // arrow keys step edge by edge, except while typing
1798
+ if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.target.closest("textarea, input:not([type=range]), select")) return;
1799
+ if (ev.key === "ArrowRight" || ev.key === "ArrowLeft") {
1800
+ if (ev.target.id === "step") return; // the slider handles its own arrow keys
1801
+ ev.preventDefault();
1802
+ stopPlay();
1803
+ goStep(state.step + (ev.key === "ArrowRight" ? 1 : -1));
1804
+ }
1805
+ });
1793
1806
  $("#prev").addEventListener("click", () => { stopPlay(); goStep(state.step - 1); });
1794
1807
  $("#next").addEventListener("click", () => { stopPlay(); goStep(state.step + 1); });
1795
1808
  $("#play").addEventListener("click", () => {
@@ -1933,30 +1946,28 @@ async function boot() {
1933
1946
  }
1934
1947
  }
1935
1948
 
1936
- // ---- visit counting, only after consent ------------------------------------------------
1937
- function track(name, params) { if (typeof window.gtag === "function") window.gtag("event", name, params || {}); }
1938
- function startAnalytics() {
1939
- if (!CONFIG.gaId || window.gtag || location.protocol === "file:") return; // local copies are not counted
1940
- window.dataLayer = window.dataLayer || [];
1941
- window.gtag = function () { window.dataLayer.push(arguments); };
1942
- window.gtag("js", new Date());
1943
- window.gtag("config", CONFIG.gaId, { anonymize_ip: true });
1949
+ // ---- visit counting (online copy only): GoatCounter, no cookies, no personal data --------
1950
+ function track(name, params) {
1951
+ if (!window.goatcounter || typeof window.goatcounter.count !== "function") return;
1952
+ const extra = params ? " " + Object.values(params).join(" ") : "";
1953
+ window.goatcounter.count({ path: name + extra, title: name, event: true });
1954
+ }
1955
+ function startCounter() {
1956
+ if (!CONFIG.counter || location.protocol === "file:" || ["127.0.0.1", "localhost"].includes(location.hostname)) return;
1944
1957
  const s = document.createElement("script");
1945
- s.async = true; s.src = "https://www.googletagmanager.com/gtag/js?id=" + encodeURIComponent(CONFIG.gaId);
1958
+ s.async = true;
1959
+ s.dataset.goatcounter = CONFIG.counter;
1960
+ s.src = "https://gc.zgo.at/count.js";
1946
1961
  document.head.appendChild(s);
1947
1962
  }
1948
- function consent() {
1949
- if (!CONFIG.gaId) return; // no visit counting in this copy: no banner
1963
+ function privacyLink() {
1964
+ if (!CONFIG.privacyURL) return; // the imprint lies next to the privacy notice
1950
1965
  $("#privacy-link").href = CONFIG.privacyURL;
1951
- const c = store.get("dtree-explorer-consent");
1952
- if (c === "yes") { startAnalytics(); return; }
1953
- if (c === "no") return;
1954
- $("#consent").hidden = false;
1955
- $("#c-yes").addEventListener("click", () => { store.set("dtree-explorer-consent", "yes"); $("#consent").hidden = true; startAnalytics(); });
1956
- $("#c-no").addEventListener("click", () => { store.set("dtree-explorer-consent", "no"); $("#consent").hidden = true; });
1966
+ $("#legal").hidden = false;
1957
1967
  }
1958
1968
 
1959
- consent();
1969
+ startCounter();
1970
+ privacyLink();
1960
1971
  boot();
1961
1972
  </script>
1962
1973
  </body>
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dtree-explorer
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: DTree Explorer: encode a directed tree with DTree's two projections (Q1, Q2) and watch it step by step, in the browser
5
5
  Author-email: Enis Olgac <enis@digraphs.info>
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/EnisOlgac/DTree-Explorer
8
8
  Project-URL: Library, https://github.com/EnisOlgac/DTree
9
- Project-URL: Online, https://digraphs.blog/dtree-demo/
9
+ Project-URL: Online, https://dtree.digraphs.net/
10
10
  Project-URL: Paper, https://doi.org/10.5772/intechopen.1013408
11
11
  Keywords: directed tree,tree topology,reachability,visualization,incremental encoding,teaching
12
12
  Classifier: Development Status :: 4 - Beta
@@ -32,7 +32,7 @@ Every tree is computed by the published library, [dtree-encoding](https://pypi.o
32
32
 
33
33
  ## Three ways to use it
34
34
 
35
- **Online:** [digraphs.blog/dtree-demo](https://digraphs.blog/dtree-demo/), nothing to install.
35
+ **Online:** [dtree.digraphs.net](https://dtree.digraphs.net/), nothing to install.
36
36
 
37
37
  **As a Python program**, offline, with the library installed on your machine:
38
38
 
@@ -59,11 +59,11 @@ The examples include the trees of the paper: the introductory example, Figure 1,
59
59
 
60
60
  ## Privacy
61
61
 
62
- Your tree stays on your computer. The page counts visits only in the copy on digraphs.blog, with Google Analytics and only after you agree; the package and the release file count nothing.
62
+ Your tree stays on your computer. Only the online copy at dtree.digraphs.net counts visits, with [GoatCounter](https://www.goatcounter.com/): no cookies, no personal data. The package and the release file count nothing.
63
63
 
64
64
  ## Building
65
65
 
66
- Working in this repository needs the library installed: `pip install dtree-encoding`. `src/template.html` is the page, `dtree_explorer/bridge.py` the Python part it runs, `src/build.py` assembles `dtree_explorer/explorer.html` (package and release) and `site/index.html` (digraphs.blog) with the installed dtree-encoding. Tests: `python -m unittest discover -s tests`.
66
+ Working in this repository needs the library installed: `pip install dtree-encoding`. `src/template.html` is the page, `dtree_explorer/bridge.py` the Python part it runs, `src/build.py` assembles `dtree_explorer/explorer.html` (package and release) and `site/index.html` (dtree.digraphs.net) with the installed dtree-encoding. Tests: `python -m unittest discover -s tests`.
67
67
 
68
68
  ## Background
69
69
 
@@ -30,7 +30,7 @@ dtree-explorer = "dtree_explorer.__main__:main"
30
30
  [project.urls]
31
31
  Homepage = "https://github.com/EnisOlgac/DTree-Explorer"
32
32
  Library = "https://github.com/EnisOlgac/DTree"
33
- Online = "https://digraphs.blog/dtree-demo/"
33
+ Online = "https://dtree.digraphs.net/"
34
34
  Paper = "https://doi.org/10.5772/intechopen.1013408"
35
35
 
36
36
  [tool.setuptools]
@@ -89,11 +89,12 @@ class TestServer(unittest.TestCase):
89
89
 
90
90
  class TestPages(unittest.TestCase):
91
91
  def test_variants(self):
92
- plain, site = build.page(), build.page(build.SITE["ga"], build.SITE["privacy"])
92
+ plain, site = build.page(), build.page(build.SITE["counter"], build.SITE["privacy"])
93
93
  for html in (plain, site):
94
94
  self.assertNotRegex(html, r"__[A-Z_]+__")
95
- self.assertNotIn(build.SITE["ga"], plain) # package copy: no visit counting
96
- self.assertIn(build.SITE["ga"], site)
95
+ self.assertNotIn("googletagmanager", html) # no Google Analytics anywhere
96
+ self.assertNotIn(build.SITE["counter"], plain) # package copy: no visit counting
97
+ self.assertIn(build.SITE["counter"], site)
97
98
 
98
99
  def test_save(self):
99
100
  with tempfile.TemporaryDirectory() as d:
File without changes