kaxe 1.5.7.dev0__tar.gz → 1.6.0.dev0__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 (147) hide show
  1. {kaxe-1.5.7.dev0/src/kaxe.egg-info → kaxe-1.6.0.dev0}/PKG-INFO +1 -1
  2. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/pyproject.toml +1 -1
  3. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/helper.py +35 -0
  4. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/shapes.py +16 -0
  5. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/svg.py +63 -5
  6. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/text.py +30 -1
  7. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d2/contour.py +75 -27
  8. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d2/inequality.py +99 -3
  9. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0/src/kaxe.egg-info}/PKG-INFO +1 -1
  10. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/LICENSE +0 -0
  11. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/MANIFEST.in +0 -0
  12. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/README.md +0 -0
  13. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/setup.cfg +0 -0
  14. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/__init__.py +0 -0
  15. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/_require_3d.py +0 -0
  16. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/chart/__init__.py +0 -0
  17. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/chart/bar.py +0 -0
  18. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/chart/box.py +0 -0
  19. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/chart/pie.py +0 -0
  20. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/chart/qqplot.py +0 -0
  21. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/__init__.py +0 -0
  22. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/axis.py +0 -0
  23. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/bounds.py +0 -0
  24. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/color.py +0 -0
  25. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/d3/backend.py +0 -0
  26. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/d3/camera.py +0 -0
  27. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/d3/helper.py +0 -0
  28. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/d3/hud.py +0 -0
  29. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/d3/objects/__init__.py +0 -0
  30. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/d3/objects/color.py +0 -0
  31. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/d3/objects/line.py +0 -0
  32. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/d3/objects/point.py +0 -0
  33. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/d3/objects/pointer.py +0 -0
  34. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/d3/objects/triangle.py +0 -0
  35. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/d3/openglrender.py +0 -0
  36. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/d3/translator.py +0 -0
  37. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/draw.py +0 -0
  38. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/fileloader.py +0 -0
  39. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/ipython_display.py +0 -0
  40. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/legend.py +0 -0
  41. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/line.py +0 -0
  42. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/marker.py +0 -0
  43. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/profiler.py +0 -0
  44. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/progress.py +0 -0
  45. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/round.py +0 -0
  46. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/styles.py +0 -0
  47. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/svg_pdf.py +0 -0
  48. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/symbol.py +0 -0
  49. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/core/window.py +0 -0
  50. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/data/__init__.py +0 -0
  51. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/data/excel.py +0 -0
  52. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/__init__.py +0 -0
  53. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/_lazy.py +0 -0
  54. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d2/__init__.py +0 -0
  55. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d2/arrow.py +0 -0
  56. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d2/bubble.py +0 -0
  57. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d2/equation.py +0 -0
  58. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d2/fill.py +0 -0
  59. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d2/function.py +0 -0
  60. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d2/map.py +0 -0
  61. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d2/parameter.py +0 -0
  62. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d2/pillar.py +0 -0
  63. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d2/point.py +0 -0
  64. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d3/__init__.py +0 -0
  65. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d3/base.py +0 -0
  66. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d3/function.py +0 -0
  67. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d3/mesh.py +0 -0
  68. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d3/point.py +0 -0
  69. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/d3/potato.py +0 -0
  70. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/function.py +0 -0
  71. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/legend.py +0 -0
  72. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/mapdata.py +0 -0
  73. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/point.py +0 -0
  74. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/objects/text.py +0 -0
  75. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/__init__.py +0 -0
  76. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/_lazy.py +0 -0
  77. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/box.py +0 -0
  78. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/constants.py +0 -0
  79. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/d3/__init__.py +0 -0
  80. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/d3/axes.py +0 -0
  81. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/d3/geometry.py +0 -0
  82. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/d3/plot3d.py +0 -0
  83. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/d3/variants.py +0 -0
  84. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/double.py +0 -0
  85. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/empty.py +0 -0
  86. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/grid.py +0 -0
  87. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/log.py +0 -0
  88. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/polar.py +0 -0
  89. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/standard.py +0 -0
  90. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/themes.py +0 -0
  91. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/zoom.py +0 -0
  92. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/plot/zoom_connector.py +0 -0
  93. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/project/__init__.py +0 -0
  94. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/project/codec.py +0 -0
  95. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/project/context.py +0 -0
  96. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/project/document.py +0 -0
  97. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/project/registry.py +0 -0
  98. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/project/sample.py +0 -0
  99. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/project/sampled_curve.py +0 -0
  100. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/project/serializers.py +0 -0
  101. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/project/window.py +0 -0
  102. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/__init__.py +0 -0
  103. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/__init__.py +0 -0
  104. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.bright-oblique.ttf +0 -0
  105. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.bright-roman.ttf +0 -0
  106. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.bright-semibold.ttf +0 -0
  107. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.bright-semiboldoblique.ttf +0 -0
  108. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.classical-serif-italic.ttf +0 -0
  109. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.concrete-bold.ttf +0 -0
  110. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.concrete-bolditalic.ttf +0 -0
  111. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.concrete-italic.ttf +0 -0
  112. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.concrete-roman.ttf +0 -0
  113. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-bold.ttf +0 -0
  114. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-boldoblique.ttf +0 -0
  115. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-demi-condensed-demicondensed.ttf +0 -0
  116. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-medium.ttf +0 -0
  117. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-oblique.ttf +0 -0
  118. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-bold.ttf +0 -0
  119. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-bolditalic.ttf +0 -0
  120. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-boldslanted.ttf +0 -0
  121. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-romanslanted.ttf +0 -0
  122. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-italic.ttf +0 -0
  123. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-roman.ttf +0 -0
  124. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-upright-italic-uprightitalic.ttf +0 -0
  125. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bold.ttf +0 -0
  126. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bolditalic.ttf +0 -0
  127. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-italic.ttf +0 -0
  128. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-light.ttf +0 -0
  129. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-lightoblique.ttf +0 -0
  130. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-regular.ttf +0 -0
  131. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-italic.ttf +0 -0
  132. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-medium.ttf +0 -0
  133. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/computer-modern-family/readme.txt +0 -0
  134. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/logo-small.png +0 -0
  135. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/symbolcross.png +0 -0
  136. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/symboldonut.png +0 -0
  137. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/symbollollipop.png +0 -0
  138. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe/resources/symboltriangle.png +0 -0
  139. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe.egg-info/SOURCES.txt +0 -0
  140. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe.egg-info/dependency_links.txt +0 -0
  141. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe.egg-info/requires.txt +0 -0
  142. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/src/kaxe.egg-info/top_level.txt +0 -0
  143. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/tests/test.py +0 -0
  144. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/tests/test2.py +0 -0
  145. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/tests/test3.py +0 -0
  146. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/tests/test_4.py +0 -0
  147. {kaxe-1.5.7.dev0 → kaxe-1.6.0.dev0}/tests/test_5.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kaxe
3
- Version: 1.5.7.dev0
3
+ Version: 1.6.0.dev0
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.7.dev0"
3
+ version = "1.6.0.dev0"
4
4
  authors = [
5
5
  { name="Valter Yde Daugberg", email="valteryde@hotmail.com" },
6
6
  ]
@@ -87,6 +87,41 @@ def contour_label_angle(func, parent, px, py, polyline=None, tangent_window=40):
87
87
  return rotation
88
88
 
89
89
 
90
+ def bbox_intersects_box(bbox, box_ltrb):
91
+ """Return True if bbox intersects box_ltrb.
92
+
93
+ ``bbox`` is ``(left, top, width, height)``; ``box_ltrb`` is
94
+ ``[left, top, right, bottom]`` (same as ``windowBox``).
95
+ """
96
+ left, top, width, height = bbox
97
+ right = left + width
98
+ bottom = top + height
99
+ box_left, box_top, box_right, box_bottom = box_ltrb
100
+ return not (
101
+ right <= box_left
102
+ or left >= box_right
103
+ or bottom <= box_top
104
+ or top >= box_bottom
105
+ )
106
+
107
+
108
+ def intersect_bbox_with_box(bbox, box_ltrb):
109
+ """Return intersection of bbox with box_ltrb as (left, top, w, h), or None."""
110
+ left, top, width, height = bbox
111
+ right = left + width
112
+ bottom = top + height
113
+ box_left, box_top, box_right, box_bottom = box_ltrb
114
+
115
+ ix0 = max(left, box_left)
116
+ iy0 = max(top, box_top)
117
+ ix1 = min(right, box_right)
118
+ iy1 = min(bottom, box_bottom)
119
+
120
+ if ix0 >= ix1 or iy0 >= iy1:
121
+ return None
122
+ return (int(ix0), int(iy0), int(ix1 - ix0), int(iy1 - iy0))
123
+
124
+
90
125
  def bbox_overlaps(a, b, padding=0):
91
126
  """Return True if two axis-aligned boxes overlap.
92
127
 
@@ -33,6 +33,22 @@ def blitImageToSurface(surface:Image, image:Image, pos:Union[tuple, list]):
33
33
  return surface.paste(image, (int(pos[0]), int(pos[1])), image)
34
34
 
35
35
 
36
+ def blit_image_clipped(surface, image, dest_left, dest_top, clip_ltrb):
37
+ """Paste ``image`` at (dest_left, dest_top), clipped to clip_ltrb [l,t,r,b]."""
38
+ from .helper import intersect_bbox_with_box
39
+
40
+ dest_bbox = (int(dest_left), int(dest_top), image.width, image.height)
41
+ intersection = intersect_bbox_with_box(dest_bbox, clip_ltrb)
42
+ if intersection is None:
43
+ return
44
+
45
+ ix, iy, iw, ih = intersection
46
+ src_x = ix - int(dest_left)
47
+ src_y = iy - int(dest_top)
48
+ cropped = image.crop((src_x, src_y, src_x + iw, src_y + ih))
49
+ blitImageToSurface(surface, cropped, (ix, iy))
50
+
51
+
36
52
  def newImage(width, height, color):
37
53
  img = Image.new('RGBA', (int(width), int(height)), color=color)
38
54
  return img
@@ -57,6 +57,8 @@ class SvgDocument:
57
57
  self._width, self._height = int(size[0]), int(size[1])
58
58
  self._elements: list[ET.Element] = []
59
59
  self._fondi_font_css: Optional[str] = None
60
+ self._clip_defs: dict[tuple[int, int, int, int], str] = {}
61
+ self._clip_counter = 0
60
62
 
61
63
  @property
62
64
  def height(self) -> int:
@@ -77,6 +79,42 @@ class SvgDocument:
77
79
  self._elements.append(el)
78
80
  return el
79
81
 
82
+ def get_clip_id(self, box_ltrb) -> str:
83
+ """Return a stable clip-path id for a kaxe [left, top, right, bottom] box."""
84
+ key = tuple(int(v) for v in box_ltrb)
85
+ if key in self._clip_defs:
86
+ return self._clip_defs[key]
87
+
88
+ clip_id = f"kaxe-clip-{self._clip_counter}"
89
+ self._clip_counter += 1
90
+ self._clip_defs[key] = clip_id
91
+ return clip_id
92
+
93
+ def _clip_rect_element(self, box_ltrb) -> ET.Element:
94
+ left, top, right, bottom = (int(v) for v in box_ltrb)
95
+ svg_y = self.flip_y(bottom)
96
+ height = bottom - top
97
+ width = right - left
98
+ return ET.Element(
99
+ f"{{{SVG_NS}}}rect",
100
+ {
101
+ "x": str(left),
102
+ "y": str(svg_y),
103
+ "width": str(width),
104
+ "height": str(height),
105
+ },
106
+ )
107
+
108
+ def append_clipped(self, element: ET.Element, box_ltrb) -> None:
109
+ """Append ``element`` inside a group clipped to ``box_ltrb``."""
110
+ clip_id = self.get_clip_id(box_ltrb)
111
+ group = ET.Element(
112
+ f"{{{SVG_NS}}}g",
113
+ {"clip-path": f"url(#{clip_id})"},
114
+ )
115
+ group.append(element)
116
+ self._elements.append(group)
117
+
80
118
  def add_rect(
81
119
  self,
82
120
  x: float,
@@ -284,6 +322,7 @@ class SvgDocument:
284
322
  y_coord: str = "bottom",
285
323
  rotate: float = 0,
286
324
  rotate_center: Optional[tuple[float, float]] = None,
325
+ clip_box: Optional[list] = None,
287
326
  ) -> None:
288
327
  """Place an image; y is in kaxe coords (y-up). y_coord='bottom' or 'top'."""
289
328
  href = pil_to_data_uri(img)
@@ -302,7 +341,14 @@ class SvgDocument:
302
341
  if rotate:
303
342
  cx, cy = rotate_center or (x + img.width / 2, svg_top + img.height / 2)
304
343
  attribs["transform"] = f"rotate({-rotate},{cx},{cy})"
305
- self.add_element("image", attribs)
344
+ el = ET.Element(
345
+ f"{{{SVG_NS}}}image",
346
+ {k: str(v) for k, v in attribs.items() if v is not None},
347
+ )
348
+ if clip_box is not None:
349
+ self.append_clipped(el, clip_box)
350
+ else:
351
+ self._elements.append(el)
306
352
 
307
353
  def _ensure_fondi_fonts(self) -> None:
308
354
  if self._fondi_font_css is not None:
@@ -319,6 +365,7 @@ class SvgDocument:
319
365
  *,
320
366
  rotate: float = 0,
321
367
  rotate_center: Optional[tuple[float, float]] = None,
368
+ clip_box: Optional[list] = None,
322
369
  ) -> None:
323
370
  """Embed a fondi scene (vector math text) at SVG top-left coordinates."""
324
371
  from fondi.backends import render_svg
@@ -347,7 +394,10 @@ class SvgDocument:
347
394
  group = ET.Element(f"{{{SVG_NS}}}g", {"transform": transform})
348
395
  for child in list(inner):
349
396
  group.append(copy.deepcopy(child))
350
- self._elements.append(group)
397
+ if clip_box is not None:
398
+ self.append_clipped(group, clip_box)
399
+ else:
400
+ self._elements.append(group)
351
401
 
352
402
  def serialize(self) -> str:
353
403
  root = ET.Element(
@@ -359,10 +409,18 @@ class SvgDocument:
359
409
  "viewBox": f"0 0 {self._width} {self._height}",
360
410
  },
361
411
  )
362
- if self._fondi_font_css is not None:
412
+ if self._fondi_font_css is not None or self._clip_defs:
363
413
  defs = ET.SubElement(root, f"{{{SVG_NS}}}defs")
364
- style = ET.SubElement(defs, f"{{{SVG_NS}}}style")
365
- style.text = self._fondi_font_css
414
+ if self._fondi_font_css is not None:
415
+ style = ET.SubElement(defs, f"{{{SVG_NS}}}style")
416
+ style.text = self._fondi_font_css
417
+ for box_ltrb, clip_id in self._clip_defs.items():
418
+ clip_path = ET.SubElement(
419
+ defs,
420
+ f"{{{SVG_NS}}}clipPath",
421
+ {"id": clip_id},
422
+ )
423
+ clip_path.append(self._clip_rect_element(box_ltrb))
366
424
  for el in self._elements:
367
425
  root.append(el)
368
426
  body = ET.tostring(root, encoding="unicode")
@@ -76,10 +76,12 @@ class Text(Shape):
76
76
  anchor_x:str="center",
77
77
  anchor_y:str="center",
78
78
  cacheImage=True,
79
+ clip_box=None,
79
80
  *args, **kwargs
80
81
  ):
81
82
 
82
83
  self.batch = batch
84
+ self.clip_box = list(clip_box) if clip_box is not None else None
83
85
  self.color = color
84
86
  self.rotate = rotate
85
87
  self.fontSize = fontSize
@@ -164,9 +166,29 @@ class Text(Shape):
164
166
  self.__center__[1] = self.__leftTop__[1] - self.height/2
165
167
 
166
168
 
169
+ def _pil_clip_box(self, surface):
170
+ if self.clip_box is None:
171
+ return None
172
+ left, top, right, bottom = self.clip_box
173
+ return [
174
+ left,
175
+ surface.height - bottom,
176
+ right,
177
+ surface.height - top,
178
+ ]
179
+
167
180
  def drawPillow(self, surface):
168
181
  [y] = flipHorizontal(surface, self.__center__[1] + self.height/2)
169
- blitImageToSurface(surface, self.img, (self.__leftTop__[0], y))
182
+ if self.clip_box is not None:
183
+ blit_image_clipped(
184
+ surface,
185
+ self.img,
186
+ self.__leftTop__[0],
187
+ y,
188
+ self._pil_clip_box(surface),
189
+ )
190
+ else:
191
+ blitImageToSurface(surface, self.img, (self.__leftTop__[0], y))
170
192
 
171
193
  def drawSvg(self, doc):
172
194
  kaxe_top = self.__center__[1] + self.height / 2
@@ -187,6 +209,7 @@ class Text(Shape):
187
209
  scene_top,
188
210
  rotate=self.rotate,
189
211
  rotate_center=rotate_center,
212
+ clip_box=self.clip_box,
190
213
  )
191
214
  return
192
215
 
@@ -197,6 +220,7 @@ class Text(Shape):
197
220
  y_coord="top",
198
221
  rotate=self.rotate,
199
222
  rotate_center=rotate_center,
223
+ clip_box=self.clip_box,
200
224
  )
201
225
 
202
226
 
@@ -208,6 +232,11 @@ class Text(Shape):
208
232
  self.__center__[1] += int(y)
209
233
  self.__leftTop__[0] += int(x)
210
234
  self.__leftTop__[1] += int(y)
235
+ if self.clip_box is not None:
236
+ self.clip_box[0] += int(x)
237
+ self.clip_box[1] += int(y)
238
+ self.clip_box[2] += int(x)
239
+ self.clip_box[3] += int(y)
211
240
 
212
241
 
213
242
  def getIncludeArguments(self):
@@ -4,7 +4,7 @@ import math
4
4
  from ...core.styles import *
5
5
  from ...core.shapes import shapes
6
6
  from ...core.symbol import symbol as symbols
7
- from ...core.helper import bbox_overlaps, contour_label_angle, resample_polyline
7
+ from ...core.helper import bbox_intersects_box, bbox_overlaps, contour_label_angle
8
8
  from ...core.text import Text
9
9
  from ...core.round import koundTeX
10
10
  from ...plot import identities
@@ -32,6 +32,56 @@ def _polyline_arc_length(polyline):
32
32
  return total
33
33
 
34
34
 
35
+ def _polyline_is_closed(polyline, tolerance=2.0):
36
+ if len(polyline) < 3:
37
+ return False
38
+ dx = polyline[0][0] - polyline[-1][0]
39
+ dy = polyline[0][1] - polyline[-1][1]
40
+ return math.hypot(dx, dy) <= tolerance
41
+
42
+
43
+ def _polyline_total_arc(polyline, closed):
44
+ arc = _polyline_arc_length(polyline)
45
+ if closed:
46
+ dx = polyline[0][0] - polyline[-1][0]
47
+ dy = polyline[0][1] - polyline[-1][1]
48
+ arc += math.hypot(dx, dy)
49
+ return arc
50
+
51
+
52
+ def _point_at_arc_length(polyline, target, closed=False):
53
+ if not polyline:
54
+ return 0, (0, 0)
55
+
56
+ total = _polyline_total_arc(polyline, closed)
57
+ if total <= 0:
58
+ return 0, polyline[0]
59
+
60
+ if closed:
61
+ target = target % total
62
+ elif target >= total:
63
+ return len(polyline) - 1, polyline[-1]
64
+
65
+ walked = 0.0
66
+ segment_count = len(polyline) - 1 + (1 if closed else 0)
67
+
68
+ for seg in range(segment_count):
69
+ i = seg if seg < len(polyline) - 1 else len(polyline) - 1
70
+ j = (seg + 1) if seg < len(polyline) - 1 else 0
71
+ dx = polyline[j][0] - polyline[i][0]
72
+ dy = polyline[j][1] - polyline[i][1]
73
+ seg_len = math.hypot(dx, dy)
74
+ if seg_len == 0:
75
+ continue
76
+ if walked + seg_len >= target:
77
+ t = (target - walked) / seg_len
78
+ point = (polyline[i][0] + t * dx, polyline[i][1] + t * dy)
79
+ return i, point
80
+ walked += seg_len
81
+
82
+ return len(polyline) - 1, polyline[-1]
83
+
84
+
35
85
  def _simplify_polyline(polyline):
36
86
  if not polyline:
37
87
  return []
@@ -47,44 +97,34 @@ def _simplify_polyline(polyline):
47
97
  return simplified
48
98
 
49
99
 
50
- def _closest_polyline_index(polyline, point):
51
- best_index = 0
52
- best_dist = float('inf')
53
- for i, (px, py) in enumerate(polyline):
54
- dist = (px - point[0]) ** 2 + (py - point[1]) ** 2
55
- if dist < best_dist:
56
- best_dist = dist
57
- best_index = i
58
- return best_index
100
+ _LABEL_PHASE_GOLDEN = 0.618033988749895
59
101
 
60
102
 
61
- def _label_candidates(polyline, spacing, max_candidates=None):
103
+ def _label_candidates(polyline, spacing, max_candidates=None, phase=0.0):
62
104
  polyline = _simplify_polyline(polyline)
63
105
  if len(polyline) < 2:
64
106
  return []
65
107
 
66
- arc = _polyline_arc_length(polyline)
108
+ closed = _polyline_is_closed(polyline)
109
+ arc = _polyline_total_arc(polyline, closed)
67
110
  if arc < spacing:
68
111
  return []
69
112
 
113
+ num_labels = max(1, int(arc / spacing))
114
+ if max_candidates is not None:
115
+ num_labels = min(num_labels, max_candidates)
116
+
70
117
  candidates = []
71
- for point in resample_polyline(polyline, spacing)[1:-1]:
72
- index = _closest_polyline_index(polyline, point)
118
+ for k in range(num_labels):
119
+ fraction = (k + 0.5 + phase) / num_labels
120
+ if closed:
121
+ fraction = fraction % 1.0
122
+ elif fraction > 1.0:
123
+ continue
124
+ target = fraction * arc
125
+ index, point = _point_at_arc_length(polyline, target, closed)
73
126
  candidates.append((point, index, arc))
74
127
 
75
- if not candidates and arc >= spacing:
76
- mid_index = len(polyline) // 2
77
- candidates.append((polyline[mid_index], mid_index, arc))
78
-
79
- if max_candidates is not None and len(candidates) > max_candidates:
80
- if max_candidates <= 1:
81
- return candidates[:1]
82
- last = len(candidates) - 1
83
- candidates = [
84
- candidates[int(i * last / (max_candidates - 1))]
85
- for i in range(max_candidates)
86
- ]
87
-
88
128
  return candidates
89
129
 
90
130
 
@@ -236,6 +276,10 @@ class Contour:
236
276
  polyline,
237
277
  self.labelSpacing,
238
278
  max_candidates=self.labelMaxPerLevel,
279
+ phase=(
280
+ level_index / max(self.steps, 1)
281
+ + (level_index * _LABEL_PHASE_GOLDEN) % 1.0
282
+ ) % 1.0,
239
283
  )
240
284
  ):
241
285
  candidates.append({
@@ -278,8 +322,12 @@ class Contour:
278
322
  rotate=round(angle),
279
323
  anchor_x='center',
280
324
  anchor_y='center',
325
+ clip_box=list(parent.windowBox),
281
326
  )
282
327
  bbox = text.getBoundingBox()
328
+ if not bbox_intersects_box(bbox, parent.windowBox):
329
+ continue
330
+
283
331
  label_padding = max(
284
332
  collision_padding,
285
333
  int(max(bbox[2], bbox[3]) * 0.15),
@@ -5,7 +5,7 @@ from ...core.color import to_rgba
5
5
  from ...core.symbol import symbol
6
6
  from ...core.helper import isRealNumber
7
7
  from ...plot import identities
8
- from .equation import Equation
8
+ from .equation import Equation, trace_contour_polylines
9
9
 
10
10
 
11
11
  def _as_2d_expr(expr):
@@ -47,6 +47,81 @@ def _eval_diff(parent, left, right, px, py):
47
47
  return None
48
48
 
49
49
 
50
+ def _point_segment_distance_sq(px, py, x1, y1, x2, y2):
51
+ dx = x2 - x1
52
+ dy = y2 - y1
53
+ if dx == 0 and dy == 0:
54
+ dpx = px - x1
55
+ dpy = py - y1
56
+ return dpx * dpx + dpy * dpy
57
+ t = max(0.0, min(1.0, ((px - x1) * dx + (py - y1) * dy) / (dx * dx + dy * dy)))
58
+ cx = x1 + t * dx
59
+ cy = y1 + t * dy
60
+ dpx = px - cx
61
+ dpy = py - cy
62
+ return dpx * dpx + dpy * dpy
63
+
64
+
65
+ def _min_distance_sq_to_segments(px, py, segments):
66
+ if not segments:
67
+ return float('inf')
68
+ return min(
69
+ _point_segment_distance_sq(px, py, x1, y1, x2, y2)
70
+ for x1, y1, x2, y2 in segments
71
+ )
72
+
73
+
74
+ def _boundary_segments(boundary, parent):
75
+ segments = []
76
+ for polyline in trace_contour_polylines(boundary.dotsPosAbstract, parent):
77
+ if len(polyline) < 2:
78
+ continue
79
+ decimated = _decimate_polyline(polyline, min_step=12)
80
+ for i in range(len(decimated) - 1):
81
+ x1, y1 = decimated[i]
82
+ x2, y2 = decimated[i + 1]
83
+ segments.append((x1, y1, x2, y2))
84
+ return segments
85
+
86
+
87
+ def _decimate_polyline(polyline, min_step=12):
88
+ if len(polyline) < 2:
89
+ return polyline
90
+ result = [polyline[0]]
91
+ last = polyline[0]
92
+ min_step_sq = min_step * min_step
93
+ for point in polyline[1:]:
94
+ dx = point[0] - last[0]
95
+ dy = point[1] - last[1]
96
+ if dx * dx + dy * dy >= min_step_sq:
97
+ result.append(point)
98
+ last = point
99
+ if result[-1] != polyline[-1]:
100
+ result.append(polyline[-1])
101
+ return result
102
+
103
+
104
+ def _build_band_cells(segments, band, x0, y0, x1, y1):
105
+ cell = max(1, int(band / 2))
106
+ band_sq = band * band
107
+ cols = int((x1 - x0) / cell) + 1
108
+ rows = int((y1 - y0) / cell) + 1
109
+ near = set()
110
+ for ci in range(cols):
111
+ for ri in range(rows):
112
+ cx = x0 + ci * cell + cell * 0.5
113
+ cy = y0 + ri * cell + cell * 0.5
114
+ if _min_distance_sq_to_segments(cx, cy, segments) <= band_sq:
115
+ near.add((ci, ri))
116
+ return cell, near, x0, y0
117
+
118
+
119
+ def _point_in_band_cells(px, py, cell, near, origin_x, origin_y):
120
+ ci = int((px - origin_x) // cell)
121
+ ri = int((py - origin_y) // cell)
122
+ return (ci, ri) in near
123
+
124
+
50
125
  class Inequality:
51
126
  """
52
127
  A class to represent a two-variable inequality ``left op right``.
@@ -77,6 +152,9 @@ class Inequality:
77
152
  hatch_angle : float, optional
78
153
  Hatch line angle in degrees, measured counter-clockwise from the
79
154
  horizontal axis (default is 45).
155
+ hatch_band : float, optional
156
+ Maximum pixel distance from the boundary to draw hatching. When
157
+ omitted, the entire forbidden side is hatched.
80
158
  computePadding : int, optional
81
159
  Extra padding when sampling the plot area (default is 50).
82
160
 
@@ -98,6 +176,7 @@ class Inequality:
98
176
  hatch_spacing=10,
99
177
  hatch_width=1,
100
178
  hatch_angle=45,
179
+ hatch_band=None,
101
180
  computePadding=50,
102
181
  ):
103
182
  self.left = _as_2d_expr(left)
@@ -106,6 +185,7 @@ class Inequality:
106
185
  self.hatch_spacing = hatch_spacing
107
186
  self.hatch_width = hatch_width
108
187
  self.hatch_angle = hatch_angle
188
+ self.hatch_band = hatch_band
109
189
  self.hatch_color = to_rgba(hatch_color)
110
190
 
111
191
  if op not in _OPS:
@@ -128,12 +208,20 @@ class Inequality:
128
208
  y0 = box[1] - self.computePadding
129
209
  y1 = box[3] + self.computePadding
130
210
 
131
- width = x1 - x0
132
- height = y1 - y0
133
211
  spacing = self.hatch_spacing
134
212
  sample_step = 2
135
213
  eps = 1e-9
136
214
 
215
+ boundary_segments = None
216
+ band_cells = None
217
+ if self.hatch_band is not None:
218
+ boundary_segments = _boundary_segments(self.boundary, parent)
219
+ if not boundary_segments:
220
+ return
221
+ band_cells = _build_band_cells(
222
+ boundary_segments, self.hatch_band, x0, y0, x1, y1
223
+ )
224
+
137
225
  scale = getattr(parent, 'getVisualScale', lambda: 1.0)()
138
226
  hatch_width = max(1, int(self.hatch_width * scale))
139
227
 
@@ -199,6 +287,14 @@ class Inequality:
199
287
  forbidden = self._is_forbidden(diff)
200
288
 
201
289
  if forbidden:
290
+ if band_cells is not None:
291
+ cell, near, origin_x, origin_y = band_cells
292
+ if not _point_in_band_cells(px, py, cell, near, origin_x, origin_y):
293
+ if segment_start is not None and last_point is not None:
294
+ self.__add_hatch_segment__(segment_start, last_point, hatch_width)
295
+ segment_start = None
296
+ last_point = None
297
+ continue
202
298
  point = (px, py)
203
299
  if segment_start is None:
204
300
  segment_start = point
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kaxe
3
- Version: 1.5.7.dev0
3
+ Version: 1.6.0.dev0
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes