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.
Files changed (145) hide show
  1. {kaxe-1.5.2.dev0/src/kaxe.egg-info → kaxe-1.5.2.dev1}/PKG-INFO +1 -1
  2. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/pyproject.toml +1 -1
  3. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/__init__.py +1 -0
  4. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/window.py +7 -1
  5. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/d3/plot3d.py +5 -1
  6. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/grid.py +5 -1
  7. kaxe-1.5.2.dev1/src/kaxe/project/__init__.py +35 -0
  8. kaxe-1.5.2.dev1/src/kaxe/project/codec.py +136 -0
  9. kaxe-1.5.2.dev1/src/kaxe/project/context.py +24 -0
  10. kaxe-1.5.2.dev1/src/kaxe/project/document.py +47 -0
  11. kaxe-1.5.2.dev1/src/kaxe/project/registry.py +33 -0
  12. kaxe-1.5.2.dev1/src/kaxe/project/sample.py +72 -0
  13. kaxe-1.5.2.dev1/src/kaxe/project/sampled_curve.py +82 -0
  14. kaxe-1.5.2.dev1/src/kaxe/project/serializers.py +298 -0
  15. kaxe-1.5.2.dev1/src/kaxe/project/window.py +247 -0
  16. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1/src/kaxe.egg-info}/PKG-INFO +1 -1
  17. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe.egg-info/SOURCES.txt +9 -0
  18. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/LICENSE +0 -0
  19. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/MANIFEST.in +0 -0
  20. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/README.md +0 -0
  21. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/setup.cfg +0 -0
  22. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/_require_3d.py +0 -0
  23. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/chart/__init__.py +0 -0
  24. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/chart/bar.py +0 -0
  25. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/chart/box.py +0 -0
  26. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/chart/pie.py +0 -0
  27. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/chart/qqplot.py +0 -0
  28. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/__init__.py +0 -0
  29. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/axis.py +0 -0
  30. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/bounds.py +0 -0
  31. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/color.py +0 -0
  32. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/backend.py +0 -0
  33. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/camera.py +0 -0
  34. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/helper.py +0 -0
  35. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/hud.py +0 -0
  36. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/objects/__init__.py +0 -0
  37. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/objects/color.py +0 -0
  38. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/objects/line.py +0 -0
  39. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/objects/point.py +0 -0
  40. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/objects/pointer.py +0 -0
  41. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/objects/triangle.py +0 -0
  42. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/openglrender.py +0 -0
  43. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/d3/translator.py +0 -0
  44. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/draw.py +0 -0
  45. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/fileloader.py +0 -0
  46. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/helper.py +0 -0
  47. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/legend.py +0 -0
  48. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/line.py +0 -0
  49. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/marker.py +0 -0
  50. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/profiler.py +0 -0
  51. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/round.py +0 -0
  52. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/shapes.py +0 -0
  53. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/styles.py +0 -0
  54. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/svg.py +0 -0
  55. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/svg_pdf.py +0 -0
  56. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/symbol.py +0 -0
  57. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/core/text.py +0 -0
  58. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/data/__init__.py +0 -0
  59. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/data/excel.py +0 -0
  60. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/__init__.py +0 -0
  61. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/_lazy.py +0 -0
  62. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/__init__.py +0 -0
  63. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/arrow.py +0 -0
  64. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/bubble.py +0 -0
  65. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/contour.py +0 -0
  66. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/equation.py +0 -0
  67. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/fill.py +0 -0
  68. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/function.py +0 -0
  69. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/inequality.py +0 -0
  70. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/map.py +0 -0
  71. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/parameter.py +0 -0
  72. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/pillar.py +0 -0
  73. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d2/point.py +0 -0
  74. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d3/__init__.py +0 -0
  75. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d3/base.py +0 -0
  76. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d3/function.py +0 -0
  77. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d3/mesh.py +0 -0
  78. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d3/point.py +0 -0
  79. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/d3/potato.py +0 -0
  80. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/function.py +0 -0
  81. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/legend.py +0 -0
  82. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/mapdata.py +0 -0
  83. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/point.py +0 -0
  84. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/objects/text.py +0 -0
  85. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/__init__.py +0 -0
  86. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/_lazy.py +0 -0
  87. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/box.py +0 -0
  88. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/constants.py +0 -0
  89. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/d3/__init__.py +0 -0
  90. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/d3/axes.py +0 -0
  91. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/d3/geometry.py +0 -0
  92. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/d3/variants.py +0 -0
  93. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/double.py +0 -0
  94. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/empty.py +0 -0
  95. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/log.py +0 -0
  96. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/polar.py +0 -0
  97. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/standard.py +0 -0
  98. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/themes.py +0 -0
  99. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/zoom.py +0 -0
  100. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/plot/zoom_connector.py +0 -0
  101. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/__init__.py +0 -0
  102. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/__init__.py +0 -0
  103. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.bright-oblique.ttf +0 -0
  104. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.bright-roman.ttf +0 -0
  105. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.bright-semibold.ttf +0 -0
  106. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.bright-semiboldoblique.ttf +0 -0
  107. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.classical-serif-italic.ttf +0 -0
  108. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.concrete-bold.ttf +0 -0
  109. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.concrete-bolditalic.ttf +0 -0
  110. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.concrete-italic.ttf +0 -0
  111. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.concrete-roman.ttf +0 -0
  112. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-bold.ttf +0 -0
  113. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-boldoblique.ttf +0 -0
  114. {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
  115. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-medium.ttf +0 -0
  116. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-oblique.ttf +0 -0
  117. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-bold.ttf +0 -0
  118. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-bolditalic.ttf +0 -0
  119. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-boldslanted.ttf +0 -0
  120. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-romanslanted.ttf +0 -0
  121. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-italic.ttf +0 -0
  122. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-roman.ttf +0 -0
  123. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.serif-upright-italic-uprightitalic.ttf +0 -0
  124. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bold.ttf +0 -0
  125. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bolditalic.ttf +0 -0
  126. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-italic.ttf +0 -0
  127. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-light.ttf +0 -0
  128. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-lightoblique.ttf +0 -0
  129. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-regular.ttf +0 -0
  130. {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
  131. {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
  132. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/computer-modern-family/readme.txt +0 -0
  133. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/logo-small.png +0 -0
  134. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/symbolcross.png +0 -0
  135. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/symboldonut.png +0 -0
  136. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/symbollollipop.png +0 -0
  137. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe/resources/symboltriangle.png +0 -0
  138. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe.egg-info/dependency_links.txt +0 -0
  139. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe.egg-info/requires.txt +0 -0
  140. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/src/kaxe.egg-info/top_level.txt +0 -0
  141. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/tests/test.py +0 -0
  142. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/tests/test2.py +0 -0
  143. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/tests/test3.py +0 -0
  144. {kaxe-1.5.2.dev0 → kaxe-1.5.2.dev1}/tests/test_4.py +0 -0
  145. {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.dev0
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kaxe"
3
- version = "1.5.2.dev0"
3
+ version = "1.5.2.dev1"
4
4
  authors = [
5
5
  { name="Valter Yde Daugberg", email="valteryde@hotmail.com" },
6
6
  ]
@@ -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