kaxe 1.5.2.dev0__tar.gz → 1.5.2.dev1__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.5.2.dev0/src/kaxe.egg-info → kaxe-1.5.2.dev1}/PKG-INFO +1 -1
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/pyproject.toml +1 -1
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/__init__.py +1 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/window.py +7 -1
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/d3/plot3d.py +5 -1
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/grid.py +5 -1
- kaxe-1.5.2.dev1/src/kaxe/project/__init__.py +35 -0
- kaxe-1.5.2.dev1/src/kaxe/project/codec.py +136 -0
- kaxe-1.5.2.dev1/src/kaxe/project/context.py +24 -0
- kaxe-1.5.2.dev1/src/kaxe/project/document.py +47 -0
- kaxe-1.5.2.dev1/src/kaxe/project/registry.py +33 -0
- kaxe-1.5.2.dev1/src/kaxe/project/sample.py +72 -0
- kaxe-1.5.2.dev1/src/kaxe/project/sampled_curve.py +82 -0
- kaxe-1.5.2.dev1/src/kaxe/project/serializers.py +298 -0
- kaxe-1.5.2.dev1/src/kaxe/project/window.py +247 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1/src/kaxe.egg-info}/PKG-INFO +1 -1
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe.egg-info/SOURCES.txt +9 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/LICENSE +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/MANIFEST.in +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/README.md +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/setup.cfg +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/_require_3d.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/chart/__init__.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/chart/bar.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/chart/box.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/chart/pie.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/chart/qqplot.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/__init__.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/axis.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/bounds.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/color.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/backend.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/camera.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/helper.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/hud.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/objects/__init__.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/objects/color.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/objects/line.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/objects/point.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/objects/pointer.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/objects/triangle.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/openglrender.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/translator.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/draw.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/fileloader.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/helper.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/legend.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/line.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/marker.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/profiler.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/round.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/shapes.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/styles.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/svg.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/svg_pdf.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/symbol.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/text.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/data/__init__.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/data/excel.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/__init__.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/_lazy.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/__init__.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/arrow.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/bubble.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/contour.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/equation.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/fill.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/function.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/inequality.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/map.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/parameter.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/pillar.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/point.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d3/__init__.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d3/base.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d3/function.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d3/mesh.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d3/point.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d3/potato.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/function.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/legend.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/mapdata.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/point.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/text.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/__init__.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/_lazy.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/box.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/constants.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/d3/__init__.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/d3/axes.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/d3/geometry.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/d3/variants.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/double.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/empty.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/log.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/polar.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/standard.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/themes.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/zoom.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/zoom_connector.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/__init__.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/__init__.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.bright-oblique.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.bright-roman.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.bright-semibold.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.bright-semiboldoblique.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.classical-serif-italic.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.concrete-bold.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.concrete-bolditalic.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.concrete-italic.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.concrete-roman.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-bold.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-boldoblique.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-demi-condensed-demicondensed.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-medium.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-oblique.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-bold.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-bolditalic.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-boldslanted.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-romanslanted.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-italic.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-roman.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-upright-italic-uprightitalic.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bold.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bolditalic.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-italic.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-light.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-lightoblique.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-regular.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-italic.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-medium.ttf +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/readme.txt +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/logo-small.png +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/symbolcross.png +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/symboldonut.png +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/symbollollipop.png +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/symboltriangle.png +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe.egg-info/dependency_links.txt +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe.egg-info/requires.txt +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe.egg-info/top_level.txt +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/tests/test.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/tests/test2.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/tests/test3.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/tests/test_4.py +0 -0
- {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/tests/test_5.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kaxe
|
|
3
|
-
Version: 1.5.2.
|
|
3
|
+
Version: 1.5.2.dev1
|
|
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
|
|
@@ -42,6 +42,7 @@ from .core import symbol
|
|
|
42
42
|
from .core import Colormaps, SingleColormap, Colormap, to_rgba
|
|
43
43
|
from .core.window import Window, AttrObject, AttrMap, setSetting
|
|
44
44
|
from .core import Axis
|
|
45
|
+
from .project import save_project, load_project
|
|
45
46
|
|
|
46
47
|
try:
|
|
47
48
|
ipy_str = str(type(get_ipython()))
|
|
@@ -585,7 +585,7 @@ class Window(AttrObject):
|
|
|
585
585
|
|
|
586
586
|
|
|
587
587
|
# save and show
|
|
588
|
-
def save(self, fname:Union[str, BytesIO], format:Optional[str]=None):
|
|
588
|
+
def save(self, fname:Union[str, BytesIO], format:Optional[str]=None, *, project:Optional[str]=None):
|
|
589
589
|
"""
|
|
590
590
|
Save the current window image to a file.
|
|
591
591
|
|
|
@@ -598,6 +598,8 @@ class Window(AttrObject):
|
|
|
598
598
|
The filename where the image will be saved or a BytesIO object to save the image in memory.
|
|
599
599
|
format : str, optional
|
|
600
600
|
Output format: ``"png"``, ``"svg"``, or ``"pdf"``. Inferred from ``fname`` extension when omitted.
|
|
601
|
+
project : str, optional
|
|
602
|
+
If given, also write a ``.kaxe`` JSON project file to this path (editable figure source).
|
|
601
603
|
|
|
602
604
|
Examples
|
|
603
605
|
--------
|
|
@@ -607,6 +609,10 @@ class Window(AttrObject):
|
|
|
607
609
|
|
|
608
610
|
"""
|
|
609
611
|
|
|
612
|
+
if project is not None:
|
|
613
|
+
from ..project import save_project
|
|
614
|
+
save_project(self, project)
|
|
615
|
+
|
|
610
616
|
fmt = infer_format(fname, format)
|
|
611
617
|
|
|
612
618
|
if fmt == "png" and self.__bakedImage__:
|
|
@@ -493,7 +493,11 @@ class Plot3D(Plot3DAxesMixin, Window):
|
|
|
493
493
|
return self.__make_overlay__()
|
|
494
494
|
|
|
495
495
|
|
|
496
|
-
def save(self, fname: Union[str, BytesIO], format: Optional[str] = None):
|
|
496
|
+
def save(self, fname: Union[str, BytesIO], format: Optional[str] = None, *, project: Optional[str] = None):
|
|
497
|
+
if project is not None:
|
|
498
|
+
raise NotImplementedError(
|
|
499
|
+
"3D plot project files are not supported in .kaxe v1"
|
|
500
|
+
)
|
|
497
501
|
|
|
498
502
|
self.setAttr('guiWidth', self.getAttr('width'))
|
|
499
503
|
self.setAttr('guiHeight', self.getAttr('height'))
|
|
@@ -438,7 +438,11 @@ class Grid(AttrObject):
|
|
|
438
438
|
self.grid[i].append(plot)
|
|
439
439
|
|
|
440
440
|
|
|
441
|
-
def save(self, fname: Union[str, BytesIO], format: Optional[str] = None):
|
|
441
|
+
def save(self, fname: Union[str, BytesIO], format: Optional[str] = None, *, project: Optional[str] = None):
|
|
442
|
+
if project is not None:
|
|
443
|
+
raise NotImplementedError(
|
|
444
|
+
"Grid project files are not supported in .kaxe v1 (planned for a future release)"
|
|
445
|
+
)
|
|
442
446
|
fmt = infer_format(fname, format)
|
|
443
447
|
|
|
444
448
|
if fmt == "svg":
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Kaxe project file (.kaxe) save/load."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Union
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ..core.styles import resetColor
|
|
9
|
+
from . import serializers as _serializers # noqa: F401 — register type handlers
|
|
10
|
+
from .document import ProjectDocument
|
|
11
|
+
from .window import assert_project_supported
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def save_project(window, path: Union[str, Path]) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Save a plot window to a ``.kaxe`` JSON project file.
|
|
17
|
+
|
|
18
|
+
The file stores styles, titles, numeric data, and sampled curves so the
|
|
19
|
+
figure can be reopened and edited without the original script.
|
|
20
|
+
"""
|
|
21
|
+
assert_project_supported(window)
|
|
22
|
+
ProjectDocument.from_window(window).write(path)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def load_project(path: Union[str, Path]):
|
|
26
|
+
"""
|
|
27
|
+
Load a plot window from a ``.kaxe`` project file.
|
|
28
|
+
|
|
29
|
+
Returns a fresh, unbaked window ready for ``style()``, ``title()``, and ``save()``.
|
|
30
|
+
"""
|
|
31
|
+
resetColor()
|
|
32
|
+
return ProjectDocument.read(path).to_window()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
__all__ = ["save_project", "load_project", "ProjectDocument"]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""JSON-safe encoding helpers for Kaxe project files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from ..core.color import Colormap, Colormaps, to_rgba
|
|
8
|
+
from ..core.styles import ComputedAttribute
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def encode_color(value) -> Any:
|
|
12
|
+
if value is None:
|
|
13
|
+
return None
|
|
14
|
+
if isinstance(value, Colormap):
|
|
15
|
+
return encode_colormap(value)
|
|
16
|
+
if isinstance(value, str):
|
|
17
|
+
return value
|
|
18
|
+
if isinstance(value, (list, tuple)):
|
|
19
|
+
if value and isinstance(value[0], (list, tuple)):
|
|
20
|
+
return [list(to_rgba(c)) for c in value]
|
|
21
|
+
return list(to_rgba(value))
|
|
22
|
+
return list(to_rgba(value))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def encode_colormap(cmap: Colormap) -> dict:
|
|
26
|
+
for name in (
|
|
27
|
+
"standard", "green", "brown", "blue", "yellow", "cream",
|
|
28
|
+
"red", "rainbow", "purple", "orange",
|
|
29
|
+
):
|
|
30
|
+
preset = getattr(Colormaps, name, None)
|
|
31
|
+
if preset is cmap:
|
|
32
|
+
return {"preset": name}
|
|
33
|
+
steps = []
|
|
34
|
+
for step in cmap.colorGradientSteps:
|
|
35
|
+
steps.append(list(int(round(c)) for c in step))
|
|
36
|
+
return {"gradient": steps}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def decode_colormap(data: Optional[dict]) -> Colormap:
|
|
40
|
+
if not data:
|
|
41
|
+
return Colormaps.standard
|
|
42
|
+
if "preset" in data:
|
|
43
|
+
name = data["preset"]
|
|
44
|
+
preset = getattr(Colormaps, name, None)
|
|
45
|
+
if preset is None:
|
|
46
|
+
raise ValueError(f"Unknown colormap preset: {name!r}")
|
|
47
|
+
return preset
|
|
48
|
+
if "gradient" in data:
|
|
49
|
+
return Colormap(data["gradient"])
|
|
50
|
+
raise ValueError(f"Invalid colormap payload: {data!r}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def encode_attrmap_styles(attrs: dict) -> dict:
|
|
54
|
+
"""Serialize user style overrides only."""
|
|
55
|
+
out = {}
|
|
56
|
+
for scope, values in attrs.items():
|
|
57
|
+
if not values:
|
|
58
|
+
continue
|
|
59
|
+
scope_out = {}
|
|
60
|
+
for key, val in values.items():
|
|
61
|
+
if isinstance(val, ComputedAttribute):
|
|
62
|
+
continue
|
|
63
|
+
if key == "color" or key.endswith("Color"):
|
|
64
|
+
scope_out[key] = encode_color(val)
|
|
65
|
+
else:
|
|
66
|
+
scope_out[key] = _encode_value(val)
|
|
67
|
+
if scope_out:
|
|
68
|
+
out[scope] = scope_out
|
|
69
|
+
return out
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def apply_attrmap_styles(attrmap, styles: dict) -> None:
|
|
73
|
+
for scope, values in (styles or {}).items():
|
|
74
|
+
for key, val in values.items():
|
|
75
|
+
if scope == "global":
|
|
76
|
+
attrmap.setAttr(key, _decode_value(val))
|
|
77
|
+
else:
|
|
78
|
+
attrmap.setAttr(key, _decode_value(val), obj=scope)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _encode_value(val: Any) -> Any:
|
|
82
|
+
if isinstance(val, (list, tuple)):
|
|
83
|
+
return [_encode_value(v) for v in val]
|
|
84
|
+
if isinstance(val, Colormap):
|
|
85
|
+
return encode_colormap(val)
|
|
86
|
+
return val
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _decode_value(val: Any) -> Any:
|
|
90
|
+
if isinstance(val, dict) and ("preset" in val or "gradient" in val):
|
|
91
|
+
return decode_colormap(val)
|
|
92
|
+
if isinstance(val, list) and val and isinstance(val[0], list):
|
|
93
|
+
return [tuple(to_rgba(c)) for c in val]
|
|
94
|
+
if isinstance(val, list) and len(val) in (3, 4) and all(isinstance(c, (int, float)) for c in val):
|
|
95
|
+
return to_rgba(val)
|
|
96
|
+
return val
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def encode_legend(obj) -> Optional[dict]:
|
|
100
|
+
text = getattr(obj, "legendText", None)
|
|
101
|
+
if text is None:
|
|
102
|
+
return None
|
|
103
|
+
sym = getattr(obj, "legendSymbol", None)
|
|
104
|
+
sym_name = sym if isinstance(sym, str) else None
|
|
105
|
+
color = getattr(obj, "legendColor", None)
|
|
106
|
+
return {
|
|
107
|
+
"text": text,
|
|
108
|
+
"symbol": sym_name,
|
|
109
|
+
"color": encode_color(color) if color is not None else None,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def apply_legend(obj, data: Optional[dict]) -> None:
|
|
114
|
+
if not data:
|
|
115
|
+
return
|
|
116
|
+
if hasattr(obj, "legend"):
|
|
117
|
+
kwargs = {}
|
|
118
|
+
if data.get("symbol"):
|
|
119
|
+
kwargs["symbol"] = data["symbol"]
|
|
120
|
+
if data.get("color") is not None:
|
|
121
|
+
kwargs["color"] = data["color"]
|
|
122
|
+
obj.legend(data["text"], **kwargs)
|
|
123
|
+
else:
|
|
124
|
+
obj.legendText = data["text"]
|
|
125
|
+
if data.get("symbol"):
|
|
126
|
+
obj.legendSymbol = data["symbol"]
|
|
127
|
+
if data.get("color") is not None:
|
|
128
|
+
obj.legendColor = to_rgba(data["color"])
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def encode_list(values) -> list:
|
|
132
|
+
return [float(v) for v in values]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def encode_grid(data) -> list:
|
|
136
|
+
return [[float(cell) for cell in row] for row in data]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Export/import context for project serialization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ExportContext:
|
|
11
|
+
window: Any
|
|
12
|
+
sample_count: int = 2000
|
|
13
|
+
|
|
14
|
+
def plot_identity(self) -> Optional[str]:
|
|
15
|
+
return getattr(self.window, "identity", None)
|
|
16
|
+
|
|
17
|
+
def plot_window_axis(self):
|
|
18
|
+
return getattr(self.window, "windowAxis", None)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ImportContext:
|
|
23
|
+
"""Placeholder for future blob deduplication and path resolution."""
|
|
24
|
+
pass
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Kaxe project document (.kaxe JSON) encode/decode."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Union
|
|
8
|
+
|
|
9
|
+
from .context import ImportContext
|
|
10
|
+
from .window import build_document, import_window
|
|
11
|
+
|
|
12
|
+
KAXE_VERSION = 1
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ProjectDocument:
|
|
16
|
+
def __init__(self, data: dict):
|
|
17
|
+
self.data = data
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def from_window(cls, window) -> "ProjectDocument":
|
|
21
|
+
payload = build_document(window)
|
|
22
|
+
payload["kaxe_version"] = KAXE_VERSION
|
|
23
|
+
return cls(payload)
|
|
24
|
+
|
|
25
|
+
def to_window(self):
|
|
26
|
+
version = self.data.get("kaxe_version", 1)
|
|
27
|
+
if version != KAXE_VERSION:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
f"Unsupported kaxe_version {version} (this Kaxe supports version {KAXE_VERSION})"
|
|
30
|
+
)
|
|
31
|
+
return import_window(self.data, ImportContext())
|
|
32
|
+
|
|
33
|
+
def encode(self, indent: int = 2) -> str:
|
|
34
|
+
return json.dumps(self.data, indent=indent, ensure_ascii=False)
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def decode(cls, text: str) -> "ProjectDocument":
|
|
38
|
+
return cls(json.loads(text))
|
|
39
|
+
|
|
40
|
+
def write(self, path: Union[str, Path]) -> None:
|
|
41
|
+
path = Path(path)
|
|
42
|
+
path.write_text(self.encode(), encoding="utf-8")
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def read(cls, path: Union[str, Path]) -> "ProjectDocument":
|
|
46
|
+
path = Path(path)
|
|
47
|
+
return cls.decode(path.read_text(encoding="utf-8"))
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Type registry for project export/import."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Callable, Dict, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
_EXPORTERS: Dict[str, Callable] = {}
|
|
8
|
+
_IMPORTERS: Dict[str, Callable] = {}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def register_type(type_name: str, export_fn: Callable, import_fn: Callable) -> None:
|
|
12
|
+
_EXPORTERS[type_name] = export_fn
|
|
13
|
+
_IMPORTERS[type_name] = import_fn
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def export_object(obj, ctx) -> dict:
|
|
17
|
+
type_name = type(obj).__name__
|
|
18
|
+
if type_name in _EXPORTERS:
|
|
19
|
+
return _EXPORTERS[type_name](obj, ctx)
|
|
20
|
+
raise TypeError(
|
|
21
|
+
f"Object type {type_name!r} cannot be exported to a .kaxe project (v1)"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def import_object(data: dict, ctx):
|
|
26
|
+
type_name = data.get("type")
|
|
27
|
+
if not type_name or type_name not in _IMPORTERS:
|
|
28
|
+
raise ValueError(f"Unknown or missing object type in project: {type_name!r}")
|
|
29
|
+
return _IMPORTERS[type_name](data, ctx)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def has_exporter(type_name: str) -> bool:
|
|
33
|
+
return type_name in _EXPORTERS
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Sample callable curves for project export."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from typing import Callable, List, Optional, Sequence, Tuple
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from ..core.bounds import (
|
|
11
|
+
DEFAULT_DOMAIN_1D,
|
|
12
|
+
resolve_interval,
|
|
13
|
+
)
|
|
14
|
+
from ..core.helper import isRealNumber
|
|
15
|
+
from ..plot import identities
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def sample_curve(
|
|
19
|
+
f: Callable,
|
|
20
|
+
*,
|
|
21
|
+
domain: Optional[Sequence[float]],
|
|
22
|
+
window_axis,
|
|
23
|
+
plot_identity: Optional[str],
|
|
24
|
+
first_axis_log: bool = False,
|
|
25
|
+
n: int = 2000,
|
|
26
|
+
args: tuple = (),
|
|
27
|
+
kwargs: dict = None,
|
|
28
|
+
) -> Tuple[List[float], List[float]]:
|
|
29
|
+
"""
|
|
30
|
+
Sample f(x) on a uniform grid for project storage.
|
|
31
|
+
|
|
32
|
+
Returns parallel x, y lists (finite points only).
|
|
33
|
+
"""
|
|
34
|
+
kwargs = kwargs or {}
|
|
35
|
+
wa = list(window_axis or [None, None, None, None])
|
|
36
|
+
while len(wa) < 4:
|
|
37
|
+
wa.append(None)
|
|
38
|
+
|
|
39
|
+
default_x = (0.01, 10.0) if first_axis_log else DEFAULT_DOMAIN_1D
|
|
40
|
+
x0, x1 = resolve_interval(domain, wa[0], wa[1], default_x)
|
|
41
|
+
if first_axis_log and x0 <= 0:
|
|
42
|
+
x0 = 0.01
|
|
43
|
+
if x0 == x1:
|
|
44
|
+
x0 -= 0.5
|
|
45
|
+
x1 += 0.5
|
|
46
|
+
|
|
47
|
+
xs = np.linspace(x0, x1, n)
|
|
48
|
+
x_out, y_out = [], []
|
|
49
|
+
|
|
50
|
+
if plot_identity == identities.POLAR:
|
|
51
|
+
fidelity = max(n, 360)
|
|
52
|
+
for angle_deg in range(0, 360 * 100, max(1, 360 * 100 // fidelity)):
|
|
53
|
+
angle = math.radians(angle_deg / 100.0)
|
|
54
|
+
try:
|
|
55
|
+
r = f(angle, *args, **kwargs)
|
|
56
|
+
except Exception:
|
|
57
|
+
continue
|
|
58
|
+
if isRealNumber(r):
|
|
59
|
+
x_out.append(float(angle))
|
|
60
|
+
y_out.append(float(r))
|
|
61
|
+
return x_out, y_out
|
|
62
|
+
|
|
63
|
+
for x in xs:
|
|
64
|
+
try:
|
|
65
|
+
y = f(float(x), *args, **kwargs)
|
|
66
|
+
except Exception:
|
|
67
|
+
continue
|
|
68
|
+
if isRealNumber(y) and math.isfinite(float(y)):
|
|
69
|
+
x_out.append(float(x))
|
|
70
|
+
y_out.append(float(y))
|
|
71
|
+
|
|
72
|
+
return x_out, y_out
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Curve from stored x/y samples (reload of exported Function2D)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..core.shapes import shapes
|
|
6
|
+
from ..core.helper import isRealNumber
|
|
7
|
+
from ..plot import identities
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SampledCurve2D:
|
|
11
|
+
"""Line curve from project file samples; preserves line style metadata."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
x,
|
|
16
|
+
y,
|
|
17
|
+
color,
|
|
18
|
+
thickness=10,
|
|
19
|
+
dotted=0,
|
|
20
|
+
dashed=0,
|
|
21
|
+
):
|
|
22
|
+
self.x = list(x)
|
|
23
|
+
self.y = list(y)
|
|
24
|
+
self.color = color
|
|
25
|
+
self.thickness = thickness
|
|
26
|
+
self.dotted = dotted
|
|
27
|
+
self.dashed = dashed
|
|
28
|
+
self.batch = shapes.Batch()
|
|
29
|
+
self.supports = [identities.XYPLOT, identities.POLAR, identities.LOGPLOT]
|
|
30
|
+
self.legendColor = color
|
|
31
|
+
|
|
32
|
+
def finalize(self, parent):
|
|
33
|
+
self.lineSegments = [[]]
|
|
34
|
+
last = None
|
|
35
|
+
for x, y in zip(self.x, self.y):
|
|
36
|
+
if not (isRealNumber(x) and isRealNumber(y)):
|
|
37
|
+
continue
|
|
38
|
+
px, py = parent.pixel(x, y)
|
|
39
|
+
if not px or not py:
|
|
40
|
+
continue
|
|
41
|
+
pt = parent.clamp(px, py)
|
|
42
|
+
if last is None:
|
|
43
|
+
last = [px, py]
|
|
44
|
+
continue
|
|
45
|
+
if parent.inside(px, py) or parent.inside(*last):
|
|
46
|
+
self.lineSegments[-1].append(parent.clamp(last[0], last[1]))
|
|
47
|
+
self.lineSegments[-1].append(pt)
|
|
48
|
+
else:
|
|
49
|
+
self.lineSegments.append([])
|
|
50
|
+
last = [px, py]
|
|
51
|
+
|
|
52
|
+
scale = getattr(parent, "getVisualScale", lambda: 1.0)()
|
|
53
|
+
width = max(1, int(self.thickness * scale))
|
|
54
|
+
for segment in self.lineSegments:
|
|
55
|
+
if len(segment) < 2:
|
|
56
|
+
continue
|
|
57
|
+
shapes.LineSegment(
|
|
58
|
+
segment,
|
|
59
|
+
color=self.color,
|
|
60
|
+
width=width,
|
|
61
|
+
batch=self.batch,
|
|
62
|
+
dotted=self.dotted > 0,
|
|
63
|
+
dashed=self.dashed > 0,
|
|
64
|
+
dashedDist=self.dashed,
|
|
65
|
+
dottedDist=self.dotted,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def draw(self, *args, **kwargs):
|
|
69
|
+
self.batch.draw(*args, **kwargs)
|
|
70
|
+
|
|
71
|
+
def push(self, *args, **kwargs):
|
|
72
|
+
self.batch.push(*args, **kwargs)
|
|
73
|
+
|
|
74
|
+
def legend(self, text, symbol=None, color=None):
|
|
75
|
+
from ..core.symbol import symbol as symbols
|
|
76
|
+
|
|
77
|
+
self.legendText = text
|
|
78
|
+
self.legendSymbol = symbol if symbol else symbols.LINE
|
|
79
|
+
if color is not None:
|
|
80
|
+
from ..core.color import to_rgba
|
|
81
|
+
self.legendColor = to_rgba(color)
|
|
82
|
+
return self
|