kaxe 1.7.0.dev0__tar.gz → 1.7.1.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 (146) hide show
  1. {kaxe-1.7.0.dev0/src/kaxe.egg-info → kaxe-1.7.1.dev0}/PKG-INFO +1 -1
  2. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/pyproject.toml +1 -1
  3. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/__init__.py +1 -0
  4. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/chart/box.py +113 -4
  5. kaxe-1.7.1.dev0/src/kaxe/core/thin.py +255 -0
  6. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d2/inequality.py +2 -18
  7. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0/src/kaxe.egg-info}/PKG-INFO +1 -1
  8. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe.egg-info/SOURCES.txt +1 -0
  9. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/LICENSE +0 -0
  10. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/MANIFEST.in +0 -0
  11. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/README.md +0 -0
  12. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/setup.cfg +0 -0
  13. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/_require_3d.py +0 -0
  14. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/chart/__init__.py +0 -0
  15. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/chart/bar.py +0 -0
  16. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/chart/pie.py +0 -0
  17. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/chart/qqplot.py +0 -0
  18. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/__init__.py +0 -0
  19. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/axis.py +0 -0
  20. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/bounds.py +0 -0
  21. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/color.py +0 -0
  22. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/d3/backend.py +0 -0
  23. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/d3/camera.py +0 -0
  24. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/d3/helper.py +0 -0
  25. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/d3/hud.py +0 -0
  26. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/d3/objects/__init__.py +0 -0
  27. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/d3/objects/color.py +0 -0
  28. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/d3/objects/line.py +0 -0
  29. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/d3/objects/point.py +0 -0
  30. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/d3/objects/pointer.py +0 -0
  31. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/d3/objects/triangle.py +0 -0
  32. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/d3/openglrender.py +0 -0
  33. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/d3/translator.py +0 -0
  34. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/draw.py +0 -0
  35. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/fileloader.py +0 -0
  36. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/helper.py +0 -0
  37. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/ipython_display.py +0 -0
  38. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/legend.py +0 -0
  39. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/line.py +0 -0
  40. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/marker.py +0 -0
  41. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/palette.py +0 -0
  42. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/profiler.py +0 -0
  43. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/progress.py +0 -0
  44. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/round.py +0 -0
  45. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/shapes.py +0 -0
  46. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/styles.py +0 -0
  47. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/svg.py +0 -0
  48. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/svg_pdf.py +0 -0
  49. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/symbol.py +0 -0
  50. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/text.py +0 -0
  51. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/core/window.py +0 -0
  52. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/data/__init__.py +0 -0
  53. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/data/excel.py +0 -0
  54. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/__init__.py +0 -0
  55. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/_lazy.py +0 -0
  56. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d2/__init__.py +0 -0
  57. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d2/arrow.py +0 -0
  58. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d2/bubble.py +0 -0
  59. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d2/contour.py +0 -0
  60. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d2/equation.py +0 -0
  61. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d2/fill.py +0 -0
  62. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d2/function.py +0 -0
  63. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d2/guide_grid.py +0 -0
  64. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d2/map.py +0 -0
  65. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d2/parameter.py +0 -0
  66. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d2/pillar.py +0 -0
  67. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d2/point.py +0 -0
  68. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d3/__init__.py +0 -0
  69. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d3/base.py +0 -0
  70. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d3/function.py +0 -0
  71. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d3/mesh.py +0 -0
  72. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d3/point.py +0 -0
  73. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/d3/potato.py +0 -0
  74. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/function.py +0 -0
  75. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/legend.py +0 -0
  76. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/mapdata.py +0 -0
  77. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/point.py +0 -0
  78. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/objects/text.py +0 -0
  79. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/__init__.py +0 -0
  80. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/_lazy.py +0 -0
  81. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/box.py +0 -0
  82. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/constants.py +0 -0
  83. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/d3/__init__.py +0 -0
  84. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/d3/axes.py +0 -0
  85. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/d3/geometry.py +0 -0
  86. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/d3/plot3d.py +0 -0
  87. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/d3/variants.py +0 -0
  88. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/double.py +0 -0
  89. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/empty.py +0 -0
  90. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/grid.py +0 -0
  91. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/log.py +0 -0
  92. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/polar.py +0 -0
  93. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/standard.py +0 -0
  94. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/themes.py +0 -0
  95. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/zoom.py +0 -0
  96. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/plot/zoom_connector.py +0 -0
  97. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/project/__init__.py +0 -0
  98. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/project/codec.py +0 -0
  99. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/project/context.py +0 -0
  100. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/project/document.py +0 -0
  101. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/project/registry.py +0 -0
  102. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/project/sample.py +0 -0
  103. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/project/sampled_curve.py +0 -0
  104. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/project/serializers.py +0 -0
  105. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/project/window.py +0 -0
  106. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/__init__.py +0 -0
  107. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/__init__.py +0 -0
  108. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.bright-oblique.ttf +0 -0
  109. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.bright-roman.ttf +0 -0
  110. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.bright-semibold.ttf +0 -0
  111. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.bright-semiboldoblique.ttf +0 -0
  112. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.classical-serif-italic.ttf +0 -0
  113. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.concrete-bold.ttf +0 -0
  114. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.concrete-bolditalic.ttf +0 -0
  115. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.concrete-italic.ttf +0 -0
  116. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.concrete-roman.ttf +0 -0
  117. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-bold.ttf +0 -0
  118. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-boldoblique.ttf +0 -0
  119. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-demi-condensed-demicondensed.ttf +0 -0
  120. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-medium.ttf +0 -0
  121. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-oblique.ttf +0 -0
  122. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-bold.ttf +0 -0
  123. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-bolditalic.ttf +0 -0
  124. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-boldslanted.ttf +0 -0
  125. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-romanslanted.ttf +0 -0
  126. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-italic.ttf +0 -0
  127. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-roman.ttf +0 -0
  128. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.serif-upright-italic-uprightitalic.ttf +0 -0
  129. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bold.ttf +0 -0
  130. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bolditalic.ttf +0 -0
  131. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-italic.ttf +0 -0
  132. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-light.ttf +0 -0
  133. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-lightoblique.ttf +0 -0
  134. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-regular.ttf +0 -0
  135. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-italic.ttf +0 -0
  136. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-medium.ttf +0 -0
  137. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/computer-modern-family/readme.txt +0 -0
  138. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe/resources/logo-small.png +0 -0
  139. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe.egg-info/dependency_links.txt +0 -0
  140. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe.egg-info/requires.txt +0 -0
  141. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/src/kaxe.egg-info/top_level.txt +0 -0
  142. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/tests/test.py +0 -0
  143. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/tests/test2.py +0 -0
  144. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/tests/test3.py +0 -0
  145. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/tests/test_4.py +0 -0
  146. {kaxe-1.7.0.dev0 → kaxe-1.7.1.dev0}/tests/test_5.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kaxe
3
- Version: 1.7.0.dev0
3
+ Version: 1.7.1.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.7.0.dev0"
3
+ version = "1.7.1.dev0"
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 .core.thin import thin_points
45
46
  from .project import save_project, load_project
46
47
 
47
48
  try:
@@ -9,6 +9,14 @@ from ..core.symbol import symbol as symbols
9
9
  from ..core.symbol import makeSymbolShapes
10
10
  from ..core.window import Window
11
11
 
12
+
13
+ def _overlay_y_offset(index, count, box_height, jitter_frac):
14
+ if count <= 1:
15
+ return 0.0
16
+ t = index / (count - 1)
17
+ return (t - 0.5) * jitter_frac * box_height
18
+
19
+
12
20
  class BoxPlot(Window):
13
21
  """
14
22
  Box plot for 1-d data
@@ -37,6 +45,7 @@ class BoxPlot(Window):
37
45
  self.attrmap.default('symbolHeight', ComputedAttribute(lambda m: m.getAttr('lineWidth')*4))
38
46
  self.attrmap.default('fill', True)
39
47
  self.attrmap.default('lineColor', (0,0,0,255))
48
+ self.attrmap.default('overlayJitter', 0.8)
40
49
 
41
50
  self.attrmap.submit(Axis)
42
51
  self.attrmap.submit(Marker)
@@ -46,6 +55,7 @@ class BoxPlot(Window):
46
55
  self.maxNumber = 0
47
56
  self.maxNumberAmount = 0
48
57
  self.boxplots = []
58
+ self.overlays = []
49
59
  self.numberAmount = 0
50
60
 
51
61
  self.legendsLabels = []
@@ -73,11 +83,22 @@ class BoxPlot(Window):
73
83
  boxplot["whiskers"] = (leftwhisker, rightwhisker)
74
84
  boxplot["quantile"] = (leftbox, rightbox)
75
85
 
86
+ n_boxes = len(self.boxplots)
87
+ for overlay in self.overlays:
88
+ box = overlay["box"]
89
+ if box < 0 or box >= n_boxes:
90
+ raise ValueError(
91
+ f"overlay box index {box} out of range; expected 0..{n_boxes - 1}"
92
+ )
93
+ if overlay["data"]:
94
+ min_ = min(min_, *overlay["data"])
95
+ max_ = max(max_, *overlay["data"])
96
+
76
97
  # important styles
77
98
  boxHeight = (self.getAttr('height')-self.getAttr('boxGap')*(len(self.boxplots)+1))/len(self.boxplots)
78
99
  lineWidth = self.getAttr('lineWidth')
79
100
  fill = self.getAttr('fill')
80
-
101
+ overlayJitter = self.getAttr('overlayJitter')
81
102
 
82
103
  self.axis.addStartAndEnd(min_, max_)
83
104
 
@@ -91,9 +112,27 @@ class BoxPlot(Window):
91
112
  self.axis.finalize(self)
92
113
  self.axis.autoAddMarkers(self)
93
114
 
115
+ box_geometry = {}
116
+ for user_index, boxplot in enumerate(self.boxplots):
117
+ render_index = n_boxes - 1 - user_index
118
+ median = np.median(boxplot["data"])
119
+ _, y = self.axis.get(median)
120
+ y += self.getAttr('boxGap') * (render_index + 1)
121
+ y0 = render_index * boxHeight + y
122
+ y1 = (render_index + 1) * boxHeight + y
123
+ box_geometry[user_index] = {"centerLine": (y0 + y1) / 2}
124
+
94
125
  for i, label in enumerate(self.legendsLabels):
95
126
  if label: self.legendbox.add(label, color=self.boxplots[i]["color"], symbol=self.boxplots[i]["symbol"])
96
127
 
128
+ for overlay in self.overlays:
129
+ if overlay["legend"]:
130
+ self.legendbox.add(
131
+ overlay["legend"],
132
+ color=overlay["color"],
133
+ symbol=overlay["symbol"],
134
+ )
135
+
97
136
  self.boxbatch = shapes.Batch()
98
137
  self.addDrawingFunction(self.boxbatch)
99
138
 
@@ -151,14 +190,31 @@ class BoxPlot(Window):
151
190
  shapes.Line(wxl, y0, wxl, y1, width=lineWidth, batch=self.boxbatch, color=lineColor)
152
191
  shapes.Line(wxr, y0, wxr, y1, width=lineWidth, batch=self.boxbatch, color=lineColor)
153
192
 
154
- for i in data:
193
+ for value in data:
155
194
  # Outliers: points outside the whisker range (beyond fence)
156
- if i < leftwhisker or i > rightwhisker:
195
+ if value < leftwhisker or value > rightwhisker:
157
196
  height = self.getAttr('symbolHeight')
158
197
  symbol = makeSymbolShapes(boxplot["symbol"], height, color=boxplot["color"], batch=self.boxbatch)
159
- symbol.x = self.axis.get(i)[0]
198
+ symbol.x = self.axis.get(value)[0]
160
199
  symbol.y = centerLine - height/2
161
200
 
201
+ symbolHeight = self.getAttr('symbolHeight')
202
+ for overlay in self.overlays:
203
+ geom = box_geometry[overlay["box"]]
204
+ centerLine = geom["centerLine"]
205
+ data = overlay["data"]
206
+ count = len(data)
207
+ for j, value in enumerate(data):
208
+ offset = _overlay_y_offset(j, count, boxHeight, overlayJitter)
209
+ symbol = makeSymbolShapes(
210
+ overlay["symbol"],
211
+ symbolHeight,
212
+ color=overlay["color"],
213
+ batch=self.boxbatch,
214
+ )
215
+ symbol.x = self.axis.get(value)[0]
216
+ symbol.y = centerLine + offset - symbolHeight / 2
217
+
162
218
 
163
219
 
164
220
  # title
@@ -199,6 +255,59 @@ class BoxPlot(Window):
199
255
 
200
256
  self.boxplots.append({"data": data, "color": color, "symbol": symbol})
201
257
 
258
+ def overlay(
259
+ self,
260
+ data: Union[list, tuple],
261
+ box: int = 0,
262
+ color=None,
263
+ symbol: str = symbols.CIRCLE,
264
+ legend: str = None,
265
+ ):
266
+ """
267
+ Overlay custom points on a box plot row.
268
+
269
+ Use this to highlight subgroups within a box, each with its own color
270
+ and symbol. Points are jittered vertically within the target box row.
271
+
272
+ Parameters
273
+ ----------
274
+ data : list or tuple
275
+ Numeric x-values to plot.
276
+ box : int, optional
277
+ Index of the target box in ``add()`` order (0 = first ``add()``).
278
+ color : tuple, optional
279
+ Point color. Defaults to the next series color.
280
+ symbol : str, optional
281
+ Marker symbol. Default is circle.
282
+ legend : str, optional
283
+ Legend label for this overlay series.
284
+
285
+ Returns
286
+ -------
287
+ self
288
+ Returns the chart instance for chaining.
289
+
290
+ Examples
291
+ --------
292
+ >>> chart.add([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
293
+ >>> chart.overlay([2, 3, 4], box=0, color=(220, 50, 50, 255), legend="low")
294
+ >>> chart.overlay([8, 9, 10], box=0, color=(50, 80, 220, 255), legend="high")
295
+ """
296
+
297
+ if color is None:
298
+ color = self.nextSeriesColor()
299
+
300
+ self.overlays.append(
301
+ {
302
+ "data": data,
303
+ "box": box,
304
+ "color": color,
305
+ "symbol": symbol,
306
+ "legend": legend,
307
+ }
308
+ )
309
+ return self
310
+
202
311
 
203
312
  def legends(self, *legends):
204
313
  """
@@ -0,0 +1,255 @@
1
+ """Utilities for reducing large point series before plotting."""
2
+
3
+ from typing import List, Optional, Sequence, Tuple, Union
4
+
5
+ import numpy as np
6
+
7
+ from .helper import isRealNumber
8
+
9
+
10
+ def decimate_by_distance(
11
+ points: Sequence[Tuple[float, float]],
12
+ min_distance: float,
13
+ ) -> List[Tuple[float, float]]:
14
+ """
15
+ Sequential min-distance filter on a polyline; always keeps endpoints.
16
+
17
+ Parameters
18
+ ----------
19
+ points : sequence of (x, y)
20
+ Points in traversal order.
21
+ min_distance : float
22
+ Minimum Euclidean distance between consecutive kept points.
23
+ """
24
+ if min_distance <= 0:
25
+ return list(points)
26
+ if len(points) < 2:
27
+ return list(points)
28
+
29
+ result = [points[0]]
30
+ last = points[0]
31
+ min_dist_sq = min_distance * min_distance
32
+
33
+ for point in points[1:]:
34
+ dx = point[0] - last[0]
35
+ dy = point[1] - last[1]
36
+ if dx * dx + dy * dy >= min_dist_sq:
37
+ result.append(point)
38
+ last = point
39
+
40
+ if result[-1] != points[-1]:
41
+ result.append(points[-1])
42
+
43
+ return result
44
+
45
+
46
+ def _decimate_indices_by_distance(
47
+ coords: Sequence[Tuple[float, float]],
48
+ min_distance: float,
49
+ ) -> List[int]:
50
+ if len(coords) < 2:
51
+ return list(range(len(coords)))
52
+
53
+ kept = [0]
54
+ last = coords[0]
55
+ min_dist_sq = min_distance * min_distance
56
+
57
+ for i in range(1, len(coords)):
58
+ point = coords[i]
59
+ dx = point[0] - last[0]
60
+ dy = point[1] - last[1]
61
+ if dx * dx + dy * dy >= min_dist_sq:
62
+ kept.append(i)
63
+ last = point
64
+
65
+ if kept[-1] != len(coords) - 1:
66
+ kept.append(len(coords) - 1)
67
+
68
+ return kept
69
+
70
+
71
+ def _filter_finite(
72
+ x: Sequence,
73
+ y: Sequence,
74
+ z: Optional[Sequence] = None,
75
+ ) -> Union[Tuple[list, list], Tuple[list, list, list]]:
76
+ cx, cy, cz = [], [], []
77
+ for i in range(len(x)):
78
+ if not (isRealNumber(x[i]) and isRealNumber(y[i])):
79
+ continue
80
+ if z is not None and not isRealNumber(z[i]):
81
+ continue
82
+ cx.append(x[i])
83
+ cy.append(y[i])
84
+ if z is not None:
85
+ cz.append(z[i])
86
+
87
+ if z is not None:
88
+ return cx, cy, cz
89
+ return cx, cy
90
+
91
+
92
+ def _thin_every(n: int, every: int) -> List[int]:
93
+ if every <= 0:
94
+ raise ValueError("every must be a positive integer")
95
+ if n == 0:
96
+ return []
97
+ indices = list(range(0, n, every))
98
+ if indices[-1] != n - 1:
99
+ indices.append(n - 1)
100
+ return indices
101
+
102
+
103
+ def _thin_max_points(n: int, max_points: int) -> List[int]:
104
+ if max_points <= 0:
105
+ raise ValueError("max_points must be a positive integer")
106
+ if n <= max_points:
107
+ return list(range(n))
108
+
109
+ indices = np.linspace(0, n - 1, max_points, dtype=int)
110
+ kept = []
111
+ seen = set()
112
+ for idx in indices:
113
+ i = int(idx)
114
+ if i not in seen:
115
+ seen.add(i)
116
+ kept.append(i)
117
+
118
+ if kept[0] != 0:
119
+ kept.insert(0, 0)
120
+ if kept[-1] != n - 1:
121
+ kept.append(n - 1)
122
+
123
+ return kept
124
+
125
+
126
+ def _data_to_pixel(plot, x_val: float, y_val: float) -> Tuple[float, float]:
127
+ width = plot.getAttr("width")
128
+ height = plot.getAttr("height")
129
+ x0, x1, y0, y1 = plot.windowAxis[:4]
130
+ scale_x = width / (x1 - x0)
131
+ scale_y = height / (y1 - y0)
132
+ offset_x = x0 * scale_x
133
+ offset_y = y0 * scale_y
134
+ padding = getattr(plot, "padding", [0, 0, 0, 0])
135
+ px = x_val * scale_x - offset_x + padding[0]
136
+ py = y_val * scale_y - offset_y + padding[1]
137
+ return px, py
138
+
139
+
140
+ def _ensure_plot_ready(plot) -> None:
141
+ if not hasattr(plot, "getAttr"):
142
+ raise ValueError("plot must provide getAttr() for width and height")
143
+ if plot.getAttr("width") is None or plot.getAttr("height") is None:
144
+ raise ValueError("plot must have width and height set (e.g. via theme())")
145
+
146
+ window_axis = getattr(plot, "windowAxis", None)
147
+ if window_axis is None or any(v is None for v in window_axis[:4]):
148
+ raise ValueError(
149
+ "plot must have fixed axis bounds (pass explicit window limits to Plot)"
150
+ )
151
+
152
+
153
+ def _pixel_coords(plot, x: Sequence, y: Sequence) -> Tuple[List[int], List[Tuple[float, float]]]:
154
+ _ensure_plot_ready(plot)
155
+
156
+ indices = []
157
+ coords = []
158
+ for i, (xi, yi) in enumerate(zip(x, y)):
159
+ px, py = _data_to_pixel(plot, float(xi), float(yi))
160
+ indices.append(i)
161
+ coords.append((px, py))
162
+
163
+ return indices, coords
164
+
165
+
166
+ def _subset(indices: List[int], *arrays: Sequence) -> tuple:
167
+ return tuple([arrays[i][j] for j in indices] for i in range(len(arrays)))
168
+
169
+
170
+ def thin_points(
171
+ x,
172
+ y,
173
+ z=None,
174
+ *,
175
+ every: Optional[int] = None,
176
+ min_distance: Optional[float] = None,
177
+ max_points: Optional[int] = None,
178
+ space: str = "data",
179
+ plot=None,
180
+ ) -> tuple:
181
+ """
182
+ Reduce a large point series before plotting.
183
+
184
+ Provide exactly one of ``every``, ``min_distance``, or ``max_points``.
185
+
186
+ Parameters
187
+ ----------
188
+ x, y : array-like
189
+ Point coordinates in input order.
190
+ z : array-like, optional
191
+ Optional third coordinate; thinned with the same indices as x and y.
192
+ every : int, optional
193
+ Keep every nth point by index (always keeps first and last).
194
+ min_distance : float, optional
195
+ Keep points at least this far apart. Uses data coordinates by default;
196
+ with ``space="pixel"``, distance is measured in screen pixels.
197
+ max_points : int, optional
198
+ Cap the number of points via uniform index sampling (always keeps
199
+ first and last when possible).
200
+ space : str, optional
201
+ ``"data"`` (default) or ``"pixel"``. Only affects ``min_distance``.
202
+ plot : plot window, optional
203
+ Required when ``space="pixel"`` and ``min_distance`` is set. Must have
204
+ fixed axis bounds and width/height (e.g. ``kaxe.Plot([x0, x1, y0, y1])``
205
+ with ``theme()`` applied).
206
+
207
+ Returns
208
+ -------
209
+ tuple
210
+ ``(x, y)`` or ``(x, y, z)`` with the thinned coordinates.
211
+ """
212
+ modes = [
213
+ every is not None,
214
+ min_distance is not None,
215
+ max_points is not None,
216
+ ]
217
+ if sum(modes) != 1:
218
+ raise ValueError("Exactly one of every, min_distance, or max_points must be provided")
219
+
220
+ if space not in ("data", "pixel"):
221
+ raise ValueError('space must be "data" or "pixel"')
222
+
223
+ if z is None:
224
+ cx, cy = _filter_finite(x, y)
225
+ cz = None
226
+ else:
227
+ cx, cy, cz = _filter_finite(x, y, z)
228
+
229
+ n = len(cx)
230
+ if n == 0:
231
+ return (cx, cy, cz) if cz is not None else (cx, cy)
232
+
233
+ if every is not None:
234
+ kept = _thin_every(n, every)
235
+ elif max_points is not None:
236
+ kept = _thin_max_points(n, max_points)
237
+ else:
238
+ if min_distance <= 0:
239
+ raise ValueError("min_distance must be positive")
240
+ if space == "pixel":
241
+ if plot is None:
242
+ raise ValueError('plot is required when space="pixel"')
243
+ data_indices, coords = _pixel_coords(plot, cx, cy)
244
+ if len(coords) < 2:
245
+ kept = data_indices
246
+ else:
247
+ local_kept = _decimate_indices_by_distance(coords, min_distance)
248
+ kept = [data_indices[i] for i in local_kept]
249
+ else:
250
+ coords = [(float(cx[i]), float(cy[i])) for i in range(n)]
251
+ kept = _decimate_indices_by_distance(coords, min_distance)
252
+
253
+ if cz is not None:
254
+ return _subset(kept, cx, cy, cz)
255
+ return _subset(kept, cx, cy)
@@ -4,6 +4,7 @@ from ...core.shapes import shapes
4
4
  from ...core.color import to_rgba
5
5
  from ...core.symbol import symbol
6
6
  from ...core.helper import isRealNumber
7
+ from ...core.thin import decimate_by_distance
7
8
  from ...plot import identities
8
9
  from .equation import Equation, trace_contour_polylines
9
10
 
@@ -50,27 +51,10 @@ def _eval_diff(parent, left, right, px, py):
50
51
  def _boundary_band_points(boundary, parent):
51
52
  points = []
52
53
  for polyline in trace_contour_polylines(boundary.dotsPosAbstract, parent):
53
- points.extend(_decimate_polyline(polyline, min_step=8))
54
+ points.extend(decimate_by_distance(polyline, 8))
54
55
  return points
55
56
 
56
57
 
57
- def _decimate_polyline(polyline, min_step=8):
58
- if len(polyline) < 2:
59
- return polyline
60
- result = [polyline[0]]
61
- last = polyline[0]
62
- min_step_sq = min_step * min_step
63
- for point in polyline[1:]:
64
- dx = point[0] - last[0]
65
- dy = point[1] - last[1]
66
- if dx * dx + dy * dy >= min_step_sq:
67
- result.append(point)
68
- last = point
69
- if result[-1] != polyline[-1]:
70
- result.append(polyline[-1])
71
- return result
72
-
73
-
74
58
  def _build_band_cells(points, band, x0, y0, x1, y1):
75
59
  cell = max(1, int(band / 2))
76
60
  reach = band + cell * 0.7071067811865476
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kaxe
3
- Version: 1.7.0.dev0
3
+ Version: 1.7.1.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
@@ -35,6 +35,7 @@ src/kaxe/core/svg.py
35
35
  src/kaxe/core/svg_pdf.py
36
36
  src/kaxe/core/symbol.py
37
37
  src/kaxe/core/text.py
38
+ src/kaxe/core/thin.py
38
39
  src/kaxe/core/window.py
39
40
  src/kaxe/core/d3/backend.py
40
41
  src/kaxe/core/d3/camera.py
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