kaxe 1.5.2.dev1__tar.gz → 1.5.5__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 (148) hide show
  1. {kaxe-1.5.2.dev1/src/kaxe.egg-info → kaxe-1.5.5}/PKG-INFO +1 -1
  2. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/pyproject.toml +1 -1
  3. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/helper.py +68 -0
  4. kaxe-1.5.5/src/kaxe/core/ipython_display.py +32 -0
  5. kaxe-1.5.5/src/kaxe/core/progress.py +47 -0
  6. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/window.py +37 -37
  7. kaxe-1.5.5/src/kaxe/objects/d2/contour.py +305 -0
  8. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/equation.py +95 -0
  9. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/inequality.py +16 -8
  10. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/point.py +57 -2
  11. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/point.py +2 -1
  12. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/d3/plot3d.py +4 -4
  13. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/grid.py +13 -15
  14. {kaxe-1.5.2.dev1 → kaxe-1.5.5/src/kaxe.egg-info}/PKG-INFO +1 -1
  15. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe.egg-info/SOURCES.txt +2 -0
  16. kaxe-1.5.2.dev1/src/kaxe/objects/d2/contour.py +0 -113
  17. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/LICENSE +0 -0
  18. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/MANIFEST.in +0 -0
  19. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/README.md +0 -0
  20. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/setup.cfg +0 -0
  21. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/__init__.py +0 -0
  22. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/_require_3d.py +0 -0
  23. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/chart/__init__.py +0 -0
  24. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/chart/bar.py +0 -0
  25. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/chart/box.py +0 -0
  26. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/chart/pie.py +0 -0
  27. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/chart/qqplot.py +0 -0
  28. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/__init__.py +0 -0
  29. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/axis.py +0 -0
  30. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/bounds.py +0 -0
  31. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/color.py +0 -0
  32. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/backend.py +0 -0
  33. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/camera.py +0 -0
  34. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/helper.py +0 -0
  35. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/hud.py +0 -0
  36. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/objects/__init__.py +0 -0
  37. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/objects/color.py +0 -0
  38. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/objects/line.py +0 -0
  39. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/objects/point.py +0 -0
  40. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/objects/pointer.py +0 -0
  41. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/objects/triangle.py +0 -0
  42. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/openglrender.py +0 -0
  43. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/translator.py +0 -0
  44. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/draw.py +0 -0
  45. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/fileloader.py +0 -0
  46. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/legend.py +0 -0
  47. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/line.py +0 -0
  48. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/marker.py +0 -0
  49. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/profiler.py +0 -0
  50. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/round.py +0 -0
  51. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/shapes.py +0 -0
  52. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/styles.py +0 -0
  53. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/svg.py +0 -0
  54. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/svg_pdf.py +0 -0
  55. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/symbol.py +0 -0
  56. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/text.py +0 -0
  57. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/data/__init__.py +0 -0
  58. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/data/excel.py +0 -0
  59. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/__init__.py +0 -0
  60. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/_lazy.py +0 -0
  61. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/__init__.py +0 -0
  62. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/arrow.py +0 -0
  63. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/bubble.py +0 -0
  64. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/fill.py +0 -0
  65. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/function.py +0 -0
  66. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/map.py +0 -0
  67. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/parameter.py +0 -0
  68. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/pillar.py +0 -0
  69. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d3/__init__.py +0 -0
  70. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d3/base.py +0 -0
  71. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d3/function.py +0 -0
  72. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d3/mesh.py +0 -0
  73. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d3/point.py +0 -0
  74. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d3/potato.py +0 -0
  75. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/function.py +0 -0
  76. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/legend.py +0 -0
  77. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/mapdata.py +0 -0
  78. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/text.py +0 -0
  79. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/__init__.py +0 -0
  80. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/_lazy.py +0 -0
  81. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/box.py +0 -0
  82. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/constants.py +0 -0
  83. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/d3/__init__.py +0 -0
  84. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/d3/axes.py +0 -0
  85. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/d3/geometry.py +0 -0
  86. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/d3/variants.py +0 -0
  87. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/double.py +0 -0
  88. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/empty.py +0 -0
  89. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/log.py +0 -0
  90. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/polar.py +0 -0
  91. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/standard.py +0 -0
  92. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/themes.py +0 -0
  93. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/zoom.py +0 -0
  94. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/zoom_connector.py +0 -0
  95. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/__init__.py +0 -0
  96. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/codec.py +0 -0
  97. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/context.py +0 -0
  98. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/document.py +0 -0
  99. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/registry.py +0 -0
  100. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/sample.py +0 -0
  101. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/sampled_curve.py +0 -0
  102. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/serializers.py +0 -0
  103. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/window.py +0 -0
  104. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/__init__.py +0 -0
  105. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/__init__.py +0 -0
  106. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.bright-oblique.ttf +0 -0
  107. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.bright-roman.ttf +0 -0
  108. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.bright-semibold.ttf +0 -0
  109. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.bright-semiboldoblique.ttf +0 -0
  110. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.classical-serif-italic.ttf +0 -0
  111. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.concrete-bold.ttf +0 -0
  112. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.concrete-bolditalic.ttf +0 -0
  113. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.concrete-italic.ttf +0 -0
  114. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.concrete-roman.ttf +0 -0
  115. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-bold.ttf +0 -0
  116. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-boldoblique.ttf +0 -0
  117. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-demi-condensed-demicondensed.ttf +0 -0
  118. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-medium.ttf +0 -0
  119. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-oblique.ttf +0 -0
  120. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-bold.ttf +0 -0
  121. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-bolditalic.ttf +0 -0
  122. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-boldslanted.ttf +0 -0
  123. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-romanslanted.ttf +0 -0
  124. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-italic.ttf +0 -0
  125. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-roman.ttf +0 -0
  126. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-upright-italic-uprightitalic.ttf +0 -0
  127. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bold.ttf +0 -0
  128. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bolditalic.ttf +0 -0
  129. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-italic.ttf +0 -0
  130. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-light.ttf +0 -0
  131. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-lightoblique.ttf +0 -0
  132. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-regular.ttf +0 -0
  133. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-italic.ttf +0 -0
  134. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-medium.ttf +0 -0
  135. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/readme.txt +0 -0
  136. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/logo-small.png +0 -0
  137. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/symbolcross.png +0 -0
  138. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/symboldonut.png +0 -0
  139. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/symbollollipop.png +0 -0
  140. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/symboltriangle.png +0 -0
  141. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe.egg-info/dependency_links.txt +0 -0
  142. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe.egg-info/requires.txt +0 -0
  143. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe.egg-info/top_level.txt +0 -0
  144. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/tests/test.py +0 -0
  145. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/tests/test2.py +0 -0
  146. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/tests/test3.py +0 -0
  147. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/tests/test_4.py +0 -0
  148. {kaxe-1.5.2.dev1 → kaxe-1.5.5}/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.dev1
3
+ Version: 1.5.5
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.dev1"
3
+ version = "1.5.5"
4
4
  authors = [
5
5
  { name="Valter Yde Daugberg", email="valteryde@hotmail.com" },
6
6
  ]
@@ -45,6 +45,74 @@ def vlen(v):
45
45
  return math.sqrt(sum([i**2 for i in v]))
46
46
 
47
47
 
48
+ def contour_label_angle(polyline, index):
49
+ """Readable tangent angle in degrees for text placed on a contour polyline."""
50
+ n = len(polyline)
51
+ if n < 2:
52
+ return 0
53
+
54
+ if index <= 0:
55
+ p0, p1 = polyline[0], polyline[1]
56
+ elif index >= n - 1:
57
+ p0, p1 = polyline[-2], polyline[-1]
58
+ else:
59
+ p0, p1 = polyline[index - 1], polyline[index + 1]
60
+
61
+ dx = p1[0] - p0[0]
62
+ dy = p1[1] - p0[1]
63
+ if dx == 0 and dy == 0:
64
+ return 0
65
+
66
+ angle = math.degrees(math.atan2(-dy, dx))
67
+ if angle > 90:
68
+ angle -= 180
69
+ elif angle < -90:
70
+ angle += 180
71
+ return angle
72
+
73
+
74
+ def resample_polyline(points, spacing):
75
+ """
76
+ Emit points every `spacing` pixels along polyline arc length.
77
+
78
+ Points are placed at uniform screen-space intervals by interpolating
79
+ along segments, so output density is non-uniform in data/index space.
80
+ """
81
+ if spacing <= 0 or len(points) < 2:
82
+ return list(points)
83
+
84
+ result = [points[0]]
85
+ dist_since_last = 0.0
86
+ prev = points[0]
87
+
88
+ for i in range(1, len(points)):
89
+ cur = points[i]
90
+ seg_dx = cur[0] - prev[0]
91
+ seg_dy = cur[1] - prev[1]
92
+ seg_len = math.hypot(seg_dx, seg_dy)
93
+
94
+ if seg_len == 0:
95
+ prev = cur
96
+ continue
97
+
98
+ pos_on_seg = 0.0
99
+ while dist_since_last + (seg_len - pos_on_seg) >= spacing:
100
+ need = spacing - dist_since_last
101
+ pos_on_seg += need
102
+ t = pos_on_seg / seg_len
103
+ result.append((prev[0] + t * seg_dx, prev[1] + t * seg_dy))
104
+ dist_since_last = 0.0
105
+
106
+ dist_since_last += seg_len - pos_on_seg
107
+ prev = cur
108
+
109
+ last = points[-1]
110
+ if result[-1] != last:
111
+ result.append(last)
112
+
113
+ return result
114
+
115
+
48
116
  def vectorScalar(v, s):
49
117
  return [i*s for i in v]
50
118
 
@@ -0,0 +1,32 @@
1
+ """Jupyter / IPython inline display helpers."""
2
+
3
+ from io import BytesIO
4
+
5
+
6
+ def is_notebook():
7
+ from .window import terminaltype
8
+
9
+ return terminaltype in ("jupyter", "ipython")
10
+
11
+
12
+ def jupyter_display_width():
13
+ from .window import settings
14
+
15
+ return settings.get("jupyterDisplayWidth", 800)
16
+
17
+
18
+ def to_png_bytes(plot):
19
+ """Render a plot or grid to PNG bytes in memory."""
20
+ buf = BytesIO()
21
+ plot.save(buf)
22
+ return buf.getvalue()
23
+
24
+
25
+ def display_png_bytes(data, width=None):
26
+ """Display PNG bytes inline in a Jupyter or IPython session."""
27
+ from IPython import display # pyright: ignore[reportMissingModuleSource]
28
+
29
+ if width is None:
30
+ width = jupyter_display_width()
31
+ image = display.Image(data=data, width=width, unconfined=True)
32
+ display.display(image)
@@ -0,0 +1,47 @@
1
+ """Progress bar helpers for terminal and notebook rendering."""
2
+
3
+ import time
4
+
5
+ import tqdm
6
+
7
+
8
+ class _NoProgress:
9
+ def update(self, n=1):
10
+ pass
11
+
12
+ def close(self):
13
+ pass
14
+
15
+
16
+ class _DeferredProgress:
17
+ """Text progress bar that appears only after a time threshold."""
18
+
19
+ def __init__(self, total, desc, threshold):
20
+ self.total = total
21
+ self.desc = desc
22
+ self.threshold = threshold
23
+ self.start = time.time()
24
+ self.pbar = None
25
+ self.count = 0
26
+
27
+ def update(self, n=1):
28
+ self.count += n
29
+ if self.pbar is None and time.time() - self.start >= self.threshold:
30
+ self.pbar = tqdm.tqdm(total=self.total, desc=self.desc, initial=self.count)
31
+ if self.pbar is not None:
32
+ self.pbar.update(n)
33
+
34
+ def close(self):
35
+ if self.pbar is not None:
36
+ self.pbar.close()
37
+
38
+
39
+ def make_progress_bar(show, total, desc):
40
+ from .window import settings, terminaltype
41
+
42
+ if not show:
43
+ return _NoProgress()
44
+ if terminaltype == "terminal":
45
+ return tqdm.tqdm(total=total, desc=desc)
46
+ threshold = settings.get("jupyterLoadingThreshold", 1.0)
47
+ return _DeferredProgress(total, desc, threshold)
@@ -8,14 +8,9 @@ from .styles import *
8
8
  from .legend import LegendBox
9
9
  from .shapes import shapes
10
10
  from .svg import SvgDocument, infer_format, is_file_path
11
+ from .progress import make_progress_bar
12
+ from .ipython_display import display_png_bytes, is_notebook, to_png_bytes
11
13
  from PIL import Image
12
- import tqdm
13
- from random import randint
14
- import os
15
- try:
16
- from IPython import display # pyright: ignore[reportMissingModuleSource]
17
- except ImportError:
18
- pass
19
14
 
20
15
 
21
16
  """
@@ -38,7 +33,11 @@ except:
38
33
  pass
39
34
 
40
35
 
41
- settings = {"removeInfo":False}
36
+ settings = {
37
+ "removeInfo": False,
38
+ "jupyterLoadingThreshold": 1.0,
39
+ "jupyterDisplayWidth": 800,
40
+ }
42
41
 
43
42
  def compute_adjust_styles(
44
43
  procentWidth,
@@ -82,6 +81,11 @@ def setSetting(**kwargs):
82
81
  removeInfo : bool
83
82
  When ``True``, suppress progress bars and bake timing logs.
84
83
  Useful in notebooks and batch scripts.
84
+ jupyterLoadingThreshold : float
85
+ Seconds before a progress bar appears in Jupyter (default 1.0).
86
+ Fast plots stay quiet.
87
+ jupyterDisplayWidth : int
88
+ Width in pixels for inline notebook display (default 800).
85
89
 
86
90
  Examples
87
91
  --------
@@ -140,7 +144,7 @@ class Window(AttrObject):
140
144
 
141
145
  self.legendbox = LegendBox()
142
146
  self.offset = [0,0]
143
- self.showProgressBar = terminaltype == "terminal"
147
+ self.showProgressBar = terminaltype != "terminal"
144
148
  self.printDebugInfo = True
145
149
 
146
150
  if settings["removeInfo"]:
@@ -487,17 +491,17 @@ class Window(AttrObject):
487
491
  def __addInnerContent__(self):
488
492
 
489
493
  # finalizeing objects
490
- if self.showProgressBar: pbar = tqdm.tqdm(total=len(self.objects), desc='Baking')
494
+ pbar = make_progress_bar(self.showProgressBar, len(self.objects), 'Baking')
491
495
  for obj in self.objects:
492
496
  self.__callFinalizeObject__(obj)
493
- if self.showProgressBar: pbar.update()
497
+ pbar.update()
494
498
  self.addDrawingFunction(obj)
495
- if self.showProgressBar:pbar.close()
499
+ pbar.close()
496
500
 
497
501
 
498
502
  def __pillowPaint__(self, fname=None):
499
503
  startTime = time.time()
500
- if self.showProgressBar: pbar = tqdm.tqdm(total=len(self.shapes), desc='Decorating')
504
+ pbar = make_progress_bar(self.showProgressBar, len(self.shapes), 'Decorating')
501
505
 
502
506
  winSize = self.width+self.padding[0]+self.padding[2], self.height+self.padding[1]+self.padding[3]
503
507
  surface = Image.new('RGBA', winSize)
@@ -508,8 +512,7 @@ class Window(AttrObject):
508
512
 
509
513
  for shape in self.shapes:
510
514
  shape.draw(surface)
511
-
512
- if self.showProgressBar: pbar.update()
515
+ pbar.update()
513
516
 
514
517
  if fname is not None:
515
518
  if is_file_path(fname):
@@ -517,7 +520,7 @@ class Window(AttrObject):
517
520
  else:
518
521
  surface.save(fname, format="png")
519
522
 
520
- if self.showProgressBar: pbar.close()
523
+ pbar.close()
521
524
  if self.printDebugInfo: logging.info('Painted in {}s'.format(str(round(time.time() - startTime, 4))))
522
525
 
523
526
  return surface
@@ -525,7 +528,7 @@ class Window(AttrObject):
525
528
 
526
529
  def __svgPaint__(self, fname=None) -> str:
527
530
  startTime = time.time()
528
- if self.showProgressBar: pbar = tqdm.tqdm(total=len(self.shapes), desc='Decorating SVG')
531
+ pbar = make_progress_bar(self.showProgressBar, len(self.shapes), 'Decorating SVG')
529
532
 
530
533
  winSize = self.width+self.padding[0]+self.padding[2], self.height+self.padding[1]+self.padding[3]
531
534
  doc = SvgDocument(winSize)
@@ -536,7 +539,7 @@ class Window(AttrObject):
536
539
 
537
540
  for shape in self.shapes:
538
541
  shape.draw(doc)
539
- if self.showProgressBar: pbar.update()
542
+ pbar.update()
540
543
 
541
544
  xml = doc.serialize()
542
545
 
@@ -547,7 +550,7 @@ class Window(AttrObject):
547
550
  else:
548
551
  fname.write(xml.encode('utf-8'))
549
552
 
550
- if self.showProgressBar: pbar.close()
553
+ pbar.close()
551
554
  if self.printDebugInfo: logging.info('Painted SVG in {}s'.format(str(round(time.time() - startTime, 4))))
552
555
 
553
556
  return xml
@@ -555,7 +558,7 @@ class Window(AttrObject):
555
558
 
556
559
  def __pdfPaint__(self, fname=None) -> bytes:
557
560
  startTime = time.time()
558
- if self.showProgressBar: pbar = tqdm.tqdm(total=len(self.shapes), desc='Decorating PDF')
561
+ pbar = make_progress_bar(self.showProgressBar, len(self.shapes), 'Decorating PDF')
559
562
 
560
563
  winSize = self.width+self.padding[0]+self.padding[2], self.height+self.padding[1]+self.padding[3]
561
564
  doc = SvgDocument(winSize)
@@ -566,11 +569,11 @@ class Window(AttrObject):
566
569
 
567
570
  for shape in self.shapes:
568
571
  shape.draw(doc)
569
- if self.showProgressBar: pbar.update()
572
+ pbar.update()
570
573
 
571
574
  pdf_bytes = doc.to_pdf(fname)
572
575
 
573
- if self.showProgressBar: pbar.close()
576
+ pbar.close()
574
577
  if self.printDebugInfo: logging.info('Painted PDF in {}s'.format(str(round(time.time() - startTime, 4))))
575
578
 
576
579
  return pdf_bytes
@@ -651,23 +654,20 @@ class Window(AttrObject):
651
654
  --------
652
655
  >>> plt.show( )
653
656
  """
654
-
655
- fname = 'plot{}.png'.format(''.join([str(randint(0,9)) for i in range(10)]))
656
-
657
- if terminaltype != "terminal":
658
- self.save(fname)
659
- i = display.Image(filename=fname, width=800, unconfined=True)
660
- display.display(i)
661
- os.remove(fname)
662
-
657
+ data = to_png_bytes(self)
658
+ if is_notebook():
659
+ display_png_bytes(data)
663
660
  else:
664
-
665
- self.save(fname)
666
- pilImage = Image.open(fname)
667
- pilImage.show()
668
- os.remove(fname)
661
+ Image.open(BytesIO(data)).show()
662
+
663
+ def _repr_png_(self):
664
+ if not is_notebook():
665
+ return None
666
+ return to_png_bytes(self)
669
667
 
670
- return fname
668
+ def __repr__(self):
669
+ n = len(getattr(self, "objects", []))
670
+ return f"<{type(self).__name__} with {n} object(s)>"
671
671
 
672
672
  # shape
673
673
  # for at kunne overskrive den nederste funktion indføres denne
@@ -0,0 +1,305 @@
1
+
2
+ import math
3
+
4
+ from ...core.styles import *
5
+ from ...core.shapes import shapes
6
+ from ...core.symbol import symbol as symbols
7
+ from ...core.helper import contour_label_angle
8
+ from ...core.text import Text
9
+ from ...core.round import koundTeX
10
+ from ...plot import identities
11
+ from .equation import Equation, trace_contour_polylines
12
+ from types import FunctionType
13
+ from typing import Union
14
+ from ...core.color import Colormaps, Colormap, to_rgba
15
+
16
+
17
+ def _format_contour_level(z, a, b, steps):
18
+ step_size = abs(b - a) / steps if steps else abs(b - a)
19
+ if step_size > 0:
20
+ digits = max(4, int(-math.log10(step_size)) + 2)
21
+ else:
22
+ digits = 4
23
+ return str(koundTeX(round(z, digits)))
24
+
25
+
26
+ def _polyline_arc_length(polyline):
27
+ total = 0.0
28
+ for i in range(1, len(polyline)):
29
+ dx = polyline[i][0] - polyline[i - 1][0]
30
+ dy = polyline[i][1] - polyline[i - 1][1]
31
+ total += math.hypot(dx, dy)
32
+ return total
33
+
34
+
35
+ def _simplify_polyline(polyline):
36
+ if not polyline:
37
+ return []
38
+
39
+ simplified = [polyline[0]]
40
+ for point in polyline[1:]:
41
+ if point != simplified[-1]:
42
+ simplified.append(point)
43
+
44
+ if len(simplified) > 2 and simplified[0] == simplified[-1]:
45
+ simplified.pop()
46
+
47
+ return simplified
48
+
49
+
50
+ def _point_at_arc_length(polyline, target):
51
+ if not polyline:
52
+ return 0, (0, 0)
53
+
54
+ if target <= 0:
55
+ return 0, polyline[0]
56
+
57
+ walked = 0.0
58
+ for i in range(1, len(polyline)):
59
+ dx = polyline[i][0] - polyline[i - 1][0]
60
+ dy = polyline[i][1] - polyline[i - 1][1]
61
+ seg_len = math.hypot(dx, dy)
62
+ if seg_len == 0:
63
+ continue
64
+ if walked + seg_len >= target:
65
+ t = (target - walked) / seg_len
66
+ px = polyline[i - 1][0] + t * dx
67
+ py = polyline[i - 1][1] + t * dy
68
+ return i, (px, py)
69
+ walked += seg_len
70
+
71
+ return len(polyline) - 1, polyline[-1]
72
+
73
+
74
+ def _closest_polyline_index(polyline, point):
75
+ best_index = 0
76
+ best_dist = float('inf')
77
+ for i, (px, py) in enumerate(polyline):
78
+ dist = (px - point[0]) ** 2 + (py - point[1]) ** 2
79
+ if dist < best_dist:
80
+ best_dist = dist
81
+ best_index = i
82
+ return best_index
83
+
84
+
85
+ def _label_positions(polyline, spacing, max_labels=2):
86
+ polyline = _simplify_polyline(polyline)
87
+ if len(polyline) < 2:
88
+ return []
89
+
90
+ arc = _polyline_arc_length(polyline)
91
+ if arc < spacing:
92
+ return []
93
+
94
+ num_labels = min(max_labels, max(1, int(arc / spacing)))
95
+ positions = []
96
+ for k in range(num_labels):
97
+ target = (k + 0.5) * arc / num_labels
98
+ index, point = _point_at_arc_length(polyline, target)
99
+ positions.append((point, _closest_polyline_index(polyline, point)))
100
+
101
+ return positions
102
+
103
+
104
+ def _point_in_expanded_bbox(x, y, bbox, padding):
105
+ left, top, width, height = bbox
106
+ return (
107
+ left - padding <= x <= left + width + padding
108
+ and top - padding <= y <= top + height + padding
109
+ )
110
+
111
+
112
+ class Contour:
113
+ """
114
+ A class used to represent a Contour.
115
+
116
+ Parameters
117
+ ----------
118
+ func3D : FunctionType
119
+ A function that represents the 3D function to be contoured.
120
+ a : Union[int, float], optional
121
+ The starting value for the contour range (default is -20).
122
+ b : Union[int, float], optional
123
+ The ending value for the contour range (default is 20).
124
+ steps : Union[int, float], optional
125
+ The number of steps in the contour (default is 15).
126
+ colorMap : Colormap, optional
127
+ The colormap to be used for the contour (default is None, which uses the standard colormap).
128
+ lineThickness : int, optional
129
+ The thickness of the contour lines (default is 10).
130
+ computePadding: int, optional
131
+ When generating the contour equations some padding can be needed to include the edges properly (default is 50).
132
+ label : bool, optional
133
+ Draw inline level labels on contour lines (default is True).
134
+ labelSpacing : int, optional
135
+ Minimum pixel spacing between labels along a contour (default is 100).
136
+ labelColor : tuple, optional
137
+ Color of inline contour labels (default is black).
138
+
139
+ Examples
140
+ --------
141
+ >>> def example_func(x, y):
142
+ >>> return x**2 + y**2
143
+ >>> contour = Contour(example_func)
144
+ >>> plt.add(contour)
145
+
146
+ """
147
+
148
+ def __init__(
149
+ self,
150
+ func3D: FunctionType,
151
+ a: Union[int, float] = -20,
152
+ b: Union[int, float] = 20,
153
+ steps: Union[int, float] = 15,
154
+ colorMap: Colormap = None,
155
+ lineThickness: int = 2,
156
+ computePadding: int = 50,
157
+ label: bool = True,
158
+ labelSpacing: int = 100,
159
+ labelColor=(0, 0, 0, 255),
160
+ ):
161
+ self.batch = shapes.Batch()
162
+
163
+ self.func = func3D
164
+ self.a = a
165
+ self.b = b
166
+ self.steps = steps
167
+
168
+ self.lineThickness = lineThickness
169
+ self.label = label
170
+ self.labelSpacing = labelSpacing
171
+ self.labelColor = to_rgba(labelColor)
172
+
173
+ # color
174
+ if colorMap is None:
175
+ self.color = Colormaps.standard
176
+ else:
177
+ self.color = colorMap
178
+
179
+ self.legendColor = self.color.getColor(5, -10, 10)
180
+
181
+ self.supports = [identities.XYPLOT, identities.POLAR, identities.XYZPLOT]
182
+
183
+ self.__equations = []
184
+
185
+ self.computePadding = computePadding
186
+
187
+
188
+ def finalize(self, parent):
189
+ plot_parent = parent
190
+ label_parent = parent
191
+ if parent == identities.XYZPLOT:
192
+ from ...core.d3.translator import getEquivalent2DPlot
193
+ label_parent = getEquivalent2DPlot(parent)
194
+
195
+ for i in range(self.steps):
196
+ z = self.a + (self.b - self.a) * i / self.steps
197
+
198
+ eq = Equation(
199
+ lambda *args, z=z: z,
200
+ self.func,
201
+ color=self.color.getColor(z, self.a, self.b),
202
+ width=self.lineThickness,
203
+ computePadding=self.computePadding,
204
+ )
205
+ eq.finalize(plot_parent)
206
+ self.__equations.append((z, eq))
207
+
208
+ if self.label and label_parent == identities.XYPLOT:
209
+ self.__finalizeLabels__(label_parent)
210
+
211
+ def __finalizeLabels__(self, parent):
212
+ fontSize = parent.getAttr('fontSize')
213
+ padding = max(2, fontSize // 8)
214
+ label_bboxes = []
215
+
216
+ for z, eq in self.__equations:
217
+ label_text = _format_contour_level(z, self.a, self.b, self.steps)
218
+ polylines = trace_contour_polylines(eq.dotsPosAbstract, parent)
219
+ polylines = sorted(
220
+ polylines,
221
+ key=lambda polyline: _polyline_arc_length(_simplify_polyline(polyline)),
222
+ reverse=True,
223
+ )
224
+ polylines = [
225
+ polyline
226
+ for polyline in polylines
227
+ if _polyline_arc_length(_simplify_polyline(polyline)) >= self.labelSpacing
228
+ ][:3]
229
+
230
+ for polyline in polylines:
231
+ for point, index in _label_positions(polyline, self.labelSpacing):
232
+ angle = contour_label_angle(polyline, index)
233
+ text = Text(
234
+ label_text,
235
+ int(point[0]),
236
+ int(point[1]),
237
+ fontSize=fontSize,
238
+ color=self.labelColor,
239
+ rotate=int(angle),
240
+ anchor_x='center',
241
+ anchor_y='center',
242
+ )
243
+
244
+ left, top = text.getLeftTopPos()
245
+ shapes.Rectangle(
246
+ left - padding,
247
+ top - padding,
248
+ text.width + 2 * padding,
249
+ text.height + 2 * padding,
250
+ color=WHITE,
251
+ batch=self.batch,
252
+ )
253
+ text.batch = self.batch
254
+ self.batch.add(text)
255
+
256
+ label_bboxes.append(text.getBoundingBox())
257
+
258
+ if not label_bboxes:
259
+ return
260
+
261
+ suppress_padding = max(4, fontSize // 4)
262
+ for _, eq in self.__equations:
263
+ for dot in eq.dots:
264
+ for bbox in label_bboxes:
265
+ if _point_in_expanded_bbox(dot.x, dot.y, bbox, suppress_padding):
266
+ dot.hide()
267
+ break
268
+
269
+
270
+ def push(self, x, y):
271
+ for _, eq in self.__equations:
272
+ eq.push(x, y)
273
+ self.batch.push(x, y)
274
+
275
+
276
+ def draw(self, surface):
277
+ for _, eq in self.__equations:
278
+ eq.draw(surface)
279
+ self.batch.draw(surface)
280
+
281
+
282
+ def legend(self, text:str, symbol=symbols.LINE, color=None):
283
+ """
284
+ Adds a legend
285
+
286
+ Parameters
287
+ ----------
288
+ text : str
289
+ The text to be displayed in the legend.
290
+ symbol : symbols, optional
291
+ The symbol to be used in the legend.
292
+ color : optional
293
+ The color to be used for the legend text. If not provided, the default color will be used.
294
+
295
+ Returns
296
+ -------
297
+ self : object
298
+ Returns the instance of the arrow object with the updated legend.
299
+ """
300
+
301
+ self.legendText = text
302
+ self.legendSymbol = symbol
303
+ if color:
304
+ self.legendColor = to_rgba(color)
305
+ return self