kaxe 1.8.1.dev0__tar.gz → 1.8.1.dev2__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.
- {kaxe-1.8.1.dev0/src/kaxe.egg-info → kaxe-1.8.1.dev2}/PKG-INFO +3 -1
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/pyproject.toml +5 -1
- kaxe-1.8.1.dev2/src/kaxe/__main__.py +6 -0
- kaxe-1.8.1.dev2/src/kaxe/_require_viewer.py +8 -0
- kaxe-1.8.1.dev2/src/kaxe/cli.py +8 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/window.py +3 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/grid.py +3 -0
- kaxe-1.8.1.dev2/src/kaxe/viewer/__init__.py +11 -0
- kaxe-1.8.1.dev2/src/kaxe/viewer/cli.py +89 -0
- kaxe-1.8.1.dev2/src/kaxe/viewer/client.py +82 -0
- kaxe-1.8.1.dev2/src/kaxe/viewer/server.py +75 -0
- kaxe-1.8.1.dev2/src/kaxe/viewer/window.py +339 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2/src/kaxe.egg-info}/PKG-INFO +3 -1
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe.egg-info/SOURCES.txt +11 -1
- kaxe-1.8.1.dev2/src/kaxe.egg-info/entry_points.txt +2 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe.egg-info/requires.txt +3 -0
- kaxe-1.8.1.dev2/tests/test_viewer.py +140 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/LICENSE +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/MANIFEST.in +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/README.md +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/setup.cfg +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/__init__.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/_require_3d.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/chart/__init__.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/chart/bar.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/chart/box.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/chart/pie.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/chart/qqplot.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/__init__.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/axis.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/bounds.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/color.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/d3/backend.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/d3/camera.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/d3/helper.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/d3/hud.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/d3/objects/__init__.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/d3/objects/color.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/d3/objects/line.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/d3/objects/point.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/d3/objects/pointer.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/d3/objects/triangle.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/d3/openglrender.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/d3/translator.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/draw.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/fileloader.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/helper.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/ipython_display.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/legend.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/line.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/marker.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/palette.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/profiler.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/progress.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/round.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/shapes.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/styles.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/svg.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/svg_pdf.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/symbol.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/text.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/core/thin.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/data/__init__.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/data/excel.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/__init__.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/_lazy.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d2/__init__.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d2/arrow.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d2/bubble.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d2/contour.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d2/equation.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d2/fill.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d2/function.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d2/guide_grid.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d2/inequality.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d2/map.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d2/parameter.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d2/pillar.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d2/point.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d3/__init__.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d3/base.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d3/function.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d3/mesh.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d3/point.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/d3/potato.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/function.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/legend.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/mapdata.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/point.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/objects/text.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/__init__.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/_lazy.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/box.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/constants.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/d3/__init__.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/d3/axes.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/d3/geometry.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/d3/plot3d.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/d3/variants.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/double.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/empty.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/log.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/polar.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/standard.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/themes.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/zoom.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/plot/zoom_connector.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/project/__init__.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/project/codec.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/project/context.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/project/document.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/project/registry.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/project/sample.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/project/sampled_curve.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/project/serializers.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/project/window.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/__init__.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/__init__.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.bright-oblique.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.bright-roman.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.bright-semibold.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.bright-semiboldoblique.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.classical-serif-italic.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.concrete-bold.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.concrete-bolditalic.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.concrete-italic.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.concrete-roman.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-bold.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-boldoblique.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-demi-condensed-demicondensed.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-medium.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-oblique.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.serif-bold.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.serif-bolditalic.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-boldslanted.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-romanslanted.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.serif-italic.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.serif-roman.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.serif-upright-italic-uprightitalic.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bold.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bolditalic.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-italic.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-light.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-lightoblique.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-regular.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-italic.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-medium.ttf +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/computer-modern-family/readme.txt +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe/resources/logo-small.png +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe.egg-info/dependency_links.txt +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/src/kaxe.egg-info/top_level.txt +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/tests/test.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/tests/test2.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/tests/test3.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/tests/test_4.py +0 -0
- {kaxe-1.8.1.dev0 → kaxe-1.8.1.dev2}/tests/test_5.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kaxe
|
|
3
|
-
Version: 1.8.1.
|
|
3
|
+
Version: 1.8.1.dev2
|
|
4
4
|
Summary: A small graphing tool for functions, points, equations and more
|
|
5
5
|
Author-email: Valter Yde Daugberg <valteryde@hotmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/valteryde/kaxe
|
|
@@ -27,6 +27,8 @@ Requires-Dist: psutil; extra == "3d"
|
|
|
27
27
|
Provides-Extra: pdf
|
|
28
28
|
Requires-Dist: reportlab>=4.0; extra == "pdf"
|
|
29
29
|
Requires-Dist: fonttools>=4.0; extra == "pdf"
|
|
30
|
+
Provides-Extra: viewer
|
|
31
|
+
Requires-Dist: pyglet>=2.0; extra == "viewer"
|
|
30
32
|
Dynamic: license-file
|
|
31
33
|
|
|
32
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "kaxe"
|
|
3
|
-
version = "1.8.1.
|
|
3
|
+
version = "1.8.1.dev2"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="Valter Yde Daugberg", email="valteryde@hotmail.com" },
|
|
6
6
|
]
|
|
@@ -31,6 +31,10 @@ core = []
|
|
|
31
31
|
"psutil",
|
|
32
32
|
]
|
|
33
33
|
pdf = ["reportlab>=4.0", "fonttools>=4.0"]
|
|
34
|
+
viewer = ["pyglet>=2.0"]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
kaxe = "kaxe.cli:main"
|
|
34
38
|
|
|
35
39
|
[project.urls]
|
|
36
40
|
Homepage = "https://github.com/valteryde/kaxe"
|
|
@@ -11,6 +11,7 @@ from .svg import SvgDocument, infer_format, is_file_path
|
|
|
11
11
|
from .progress import make_progress_bar
|
|
12
12
|
from .ipython_display import display_png_bytes, is_notebook, to_png_bytes
|
|
13
13
|
from .styles import assign_series_color, _palette_color_at
|
|
14
|
+
from ..viewer.client import try_show_in_viewer
|
|
14
15
|
from PIL import Image
|
|
15
16
|
|
|
16
17
|
|
|
@@ -660,6 +661,8 @@ class Window(AttrObject):
|
|
|
660
661
|
data = to_png_bytes(self)
|
|
661
662
|
if is_notebook():
|
|
662
663
|
display_png_bytes(data)
|
|
664
|
+
elif try_show_in_viewer(data, title=getattr(self, "titleText", "")):
|
|
665
|
+
return
|
|
663
666
|
else:
|
|
664
667
|
Image.open(BytesIO(data)).show()
|
|
665
668
|
|
|
@@ -4,6 +4,7 @@ from typing import Optional, Union
|
|
|
4
4
|
from ..core.styles import AttrObject, AttrMap
|
|
5
5
|
from ..core.window import *
|
|
6
6
|
from ..core.ipython_display import display_png_bytes, is_notebook, to_png_bytes
|
|
7
|
+
from ..viewer.client import try_show_in_viewer
|
|
7
8
|
from ..core.legend import LegendBox
|
|
8
9
|
from ..core.svg import (
|
|
9
10
|
SvgDocument,
|
|
@@ -503,6 +504,8 @@ class Grid(AttrObject):
|
|
|
503
504
|
data = to_png_bytes(self)
|
|
504
505
|
if is_notebook():
|
|
505
506
|
display_png_bytes(data)
|
|
507
|
+
elif try_show_in_viewer(data, title="Grid"):
|
|
508
|
+
return
|
|
506
509
|
else:
|
|
507
510
|
Image.open(BytesIO(data)).show()
|
|
508
511
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Kaxe Viewer package."""
|
|
2
|
+
|
|
3
|
+
from .client import DEFAULT_PORT, get_viewer_port, is_viewer_active, send_to_viewer, try_show_in_viewer
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"DEFAULT_PORT",
|
|
7
|
+
"get_viewer_port",
|
|
8
|
+
"is_viewer_active",
|
|
9
|
+
"send_to_viewer",
|
|
10
|
+
"try_show_in_viewer",
|
|
11
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Command line interface for launching Kaxe Viewer."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import queue
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from .client import get_viewer_port, is_viewer_active
|
|
8
|
+
from .server import start_server_thread
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_viewer(port: int = 41783):
|
|
12
|
+
"""Launch the Kaxe Viewer Pyglet window and HTTP server."""
|
|
13
|
+
# Ensure optional dependency is installed
|
|
14
|
+
from .._require_viewer import require_viewer
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
require_viewer()
|
|
18
|
+
except ImportError as e:
|
|
19
|
+
print(f"\n[Kaxe] {e}\n", file=sys.stderr)
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
import pyglet
|
|
23
|
+
from .window import KaxeViewerWindow
|
|
24
|
+
|
|
25
|
+
# Check if a viewer is already running on this port
|
|
26
|
+
if is_viewer_active(port):
|
|
27
|
+
print(f"\n[Kaxe Viewer] Port {port} is already in use.", file=sys.stderr)
|
|
28
|
+
print("A Kaxe Viewer instance is likely already open.\n", file=sys.stderr)
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
|
|
31
|
+
plot_queue = queue.Queue()
|
|
32
|
+
|
|
33
|
+
# Start HTTP server on background thread
|
|
34
|
+
server, thread = start_server_thread(port, plot_queue)
|
|
35
|
+
|
|
36
|
+
print("=" * 64)
|
|
37
|
+
print(f" Kaxe Viewer running on http://127.0.0.1:{port}")
|
|
38
|
+
print(" Keep this terminal open while working.")
|
|
39
|
+
print(" In another terminal, run your scripts calling .show()!")
|
|
40
|
+
print()
|
|
41
|
+
print(" Controls:")
|
|
42
|
+
print(" • Mouse Wheel : Zoom (centered at cursor)")
|
|
43
|
+
print(" • Left Click Drag : Pan image")
|
|
44
|
+
print(" • [R] or [Space] : Fit plot to window")
|
|
45
|
+
print(" • [1] : 100% native resolution")
|
|
46
|
+
print(" • [←] / [→] : Cycle through previous plots")
|
|
47
|
+
print(" • [C] : Copy image to clipboard")
|
|
48
|
+
print(" • [S] : Save image to file")
|
|
49
|
+
print(" • [H] : Toggle HUD overlay")
|
|
50
|
+
print("=" * 64)
|
|
51
|
+
|
|
52
|
+
# Create Pyglet window and run Cocoa / OpenGL event loop on main thread
|
|
53
|
+
window = KaxeViewerWindow(plot_queue)
|
|
54
|
+
pyglet.app.run()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def main(argv=None):
|
|
58
|
+
"""Entrypoint for the `kaxe` CLI command."""
|
|
59
|
+
if argv is None:
|
|
60
|
+
argv = sys.argv[1:]
|
|
61
|
+
|
|
62
|
+
parser = argparse.ArgumentParser(
|
|
63
|
+
prog="kaxe",
|
|
64
|
+
description="Kaxe graphing & plotting toolkit CLI",
|
|
65
|
+
)
|
|
66
|
+
subparsers = parser.add_subparsers(dest="subcommand", help="Available subcommands")
|
|
67
|
+
|
|
68
|
+
# `kaxe viewer` command
|
|
69
|
+
viewer_parser = subparsers.add_parser(
|
|
70
|
+
"viewer",
|
|
71
|
+
help="Start the standalone Kaxe interactive plot viewer",
|
|
72
|
+
)
|
|
73
|
+
viewer_parser.add_argument(
|
|
74
|
+
"--port",
|
|
75
|
+
type=int,
|
|
76
|
+
default=get_viewer_port(),
|
|
77
|
+
help=f"Port to listen on (default: {get_viewer_port()})",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
args = parser.parse_args(argv)
|
|
81
|
+
|
|
82
|
+
if args.subcommand == "viewer":
|
|
83
|
+
run_viewer(port=args.port)
|
|
84
|
+
else:
|
|
85
|
+
parser.print_help()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
main()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Client-side probe and transmitter for Kaxe Viewer.
|
|
2
|
+
|
|
3
|
+
This module intentionally uses ONLY Python standard library modules
|
|
4
|
+
(socket, urllib, os) so that calling `plt.show()` never requires
|
|
5
|
+
pyglet or any other viewer dependency.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import socket
|
|
10
|
+
import urllib.error
|
|
11
|
+
import urllib.parse
|
|
12
|
+
import urllib.request
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
DEFAULT_PORT = 41783
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_viewer_port() -> int:
|
|
19
|
+
"""Return the configured viewer port from environment or the default."""
|
|
20
|
+
env_port = os.getenv("KAXE_VIEWER_PORT")
|
|
21
|
+
if env_port:
|
|
22
|
+
try:
|
|
23
|
+
return int(env_port)
|
|
24
|
+
except ValueError:
|
|
25
|
+
pass
|
|
26
|
+
return DEFAULT_PORT
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_viewer_active(port: Optional[int] = None, timeout: float = 0.03) -> bool:
|
|
30
|
+
"""Check if the Kaxe Viewer is currently listening on localhost.
|
|
31
|
+
|
|
32
|
+
Uses a fast socket connection test with a short timeout (~30ms).
|
|
33
|
+
When no viewer is running, the OS kernel rejects the connection
|
|
34
|
+
virtually instantaneously (<1ms).
|
|
35
|
+
"""
|
|
36
|
+
if port is None:
|
|
37
|
+
port = get_viewer_port()
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
with socket.create_connection(("127.0.0.1", port), timeout=timeout):
|
|
41
|
+
return True
|
|
42
|
+
except (OSError, ConnectionRefusedError):
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def send_to_viewer(
|
|
47
|
+
data: bytes,
|
|
48
|
+
title: str = "",
|
|
49
|
+
port: Optional[int] = None,
|
|
50
|
+
timeout: float = 2.0,
|
|
51
|
+
) -> bool:
|
|
52
|
+
"""Send image PNG bytes to the active Kaxe Viewer over HTTP POST.
|
|
53
|
+
|
|
54
|
+
Returns True if successfully received, False otherwise.
|
|
55
|
+
"""
|
|
56
|
+
if port is None:
|
|
57
|
+
port = get_viewer_port()
|
|
58
|
+
|
|
59
|
+
url = f"http://127.0.0.1:{port}/show"
|
|
60
|
+
safe_title = urllib.parse.quote(title or "")
|
|
61
|
+
headers = {
|
|
62
|
+
"Content-Type": "image/png",
|
|
63
|
+
"X-Kaxe-Title": safe_title,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
|
|
67
|
+
try:
|
|
68
|
+
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
69
|
+
return response.status == 200
|
|
70
|
+
except (urllib.error.URLError, OSError):
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def try_show_in_viewer(data: bytes, title: str = "") -> bool:
|
|
75
|
+
"""Convenience helper: probe port and send image if viewer is alive.
|
|
76
|
+
|
|
77
|
+
Returns True if sent to viewer, False if fallback is needed.
|
|
78
|
+
"""
|
|
79
|
+
port = get_viewer_port()
|
|
80
|
+
if is_viewer_active(port):
|
|
81
|
+
return send_to_viewer(data, title=title, port=port)
|
|
82
|
+
return False
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Lightweight HTTP server embedded inside Kaxe Viewer.
|
|
2
|
+
|
|
3
|
+
Uses Python standard library `http.server.ThreadingHTTPServer` to receive
|
|
4
|
+
plot images and metadata from client scripts without external web frameworks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
8
|
+
import queue
|
|
9
|
+
import threading
|
|
10
|
+
import urllib.parse
|
|
11
|
+
from typing import Any, Dict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PlotRequestHandler(BaseHTTPRequestHandler):
|
|
15
|
+
"""Handles incoming /show POST requests and /ping health checks."""
|
|
16
|
+
|
|
17
|
+
# Reference to the shared Queue injected by start_server
|
|
18
|
+
plot_queue: queue.Queue = queue.Queue()
|
|
19
|
+
|
|
20
|
+
def do_GET(self):
|
|
21
|
+
if self.path in ("/ping", "/health"):
|
|
22
|
+
self.send_response(200)
|
|
23
|
+
self.send_header("Content-Type", "text/plain")
|
|
24
|
+
self.end_headers()
|
|
25
|
+
self.wfile.write(b"OK")
|
|
26
|
+
else:
|
|
27
|
+
self.send_response(404)
|
|
28
|
+
self.end_headers()
|
|
29
|
+
|
|
30
|
+
def do_POST(self):
|
|
31
|
+
if self.path == "/show":
|
|
32
|
+
content_length = int(self.headers.get("Content-Length", 0))
|
|
33
|
+
if content_length <= 0:
|
|
34
|
+
self.send_response(400)
|
|
35
|
+
self.end_headers()
|
|
36
|
+
self.wfile.write(b"Empty body")
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
img_bytes = self.rfile.read(content_length)
|
|
40
|
+
raw_title = self.headers.get("X-Kaxe-Title", "")
|
|
41
|
+
title = urllib.parse.unquote(raw_title) if raw_title else ""
|
|
42
|
+
|
|
43
|
+
item: Dict[str, Any] = {
|
|
44
|
+
"data": img_bytes,
|
|
45
|
+
"title": title,
|
|
46
|
+
}
|
|
47
|
+
self.plot_queue.put(item)
|
|
48
|
+
|
|
49
|
+
self.send_response(200)
|
|
50
|
+
self.send_header("Content-Type", "text/plain")
|
|
51
|
+
self.end_headers()
|
|
52
|
+
self.wfile.write(b"OK")
|
|
53
|
+
else:
|
|
54
|
+
self.send_response(404)
|
|
55
|
+
self.end_headers()
|
|
56
|
+
|
|
57
|
+
def log_message(self, format: str, *args):
|
|
58
|
+
# Format messages cleanly in terminal
|
|
59
|
+
first_arg = args[0] if args else ""
|
|
60
|
+
if "POST /show" in str(first_arg):
|
|
61
|
+
print(f"[Kaxe Viewer] New plot received ({format % args})")
|
|
62
|
+
# Suppress noisy health checks in console
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def start_server_thread(
|
|
66
|
+
port: int,
|
|
67
|
+
plot_queue: queue.Queue,
|
|
68
|
+
host: str = "127.0.0.1",
|
|
69
|
+
) -> tuple[ThreadingHTTPServer, threading.Thread]:
|
|
70
|
+
"""Start the HTTP server on a daemon background thread."""
|
|
71
|
+
PlotRequestHandler.plot_queue = plot_queue
|
|
72
|
+
server = ThreadingHTTPServer((host, port), PlotRequestHandler)
|
|
73
|
+
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
74
|
+
thread.start()
|
|
75
|
+
return server, thread
|