kaxe 1.8.1.dev4__tar.gz → 1.8.1.dev6__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 (156) hide show
  1. {kaxe-1.8.1.dev4/src/kaxe.egg-info → kaxe-1.8.1.dev6}/PKG-INFO +4 -2
  2. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/README.md +3 -1
  3. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/pyproject.toml +1 -1
  4. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/viewer/window.py +109 -15
  5. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6/src/kaxe.egg-info}/PKG-INFO +4 -2
  6. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/tests/test_viewer.py +37 -5
  7. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/LICENSE +0 -0
  8. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/MANIFEST.in +0 -0
  9. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/setup.cfg +0 -0
  10. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/__init__.py +0 -0
  11. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/__main__.py +0 -0
  12. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/_require_3d.py +0 -0
  13. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/_require_viewer.py +0 -0
  14. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/chart/__init__.py +0 -0
  15. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/chart/bar.py +0 -0
  16. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/chart/box.py +0 -0
  17. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/chart/pie.py +0 -0
  18. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/chart/qqplot.py +0 -0
  19. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/cli.py +0 -0
  20. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/__init__.py +0 -0
  21. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/axis.py +0 -0
  22. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/bounds.py +0 -0
  23. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/color.py +0 -0
  24. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/d3/backend.py +0 -0
  25. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/d3/camera.py +0 -0
  26. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/d3/helper.py +0 -0
  27. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/d3/hud.py +0 -0
  28. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/d3/objects/__init__.py +0 -0
  29. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/d3/objects/color.py +0 -0
  30. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/d3/objects/line.py +0 -0
  31. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/d3/objects/point.py +0 -0
  32. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/d3/objects/pointer.py +0 -0
  33. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/d3/objects/triangle.py +0 -0
  34. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/d3/openglrender.py +0 -0
  35. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/d3/translator.py +0 -0
  36. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/draw.py +0 -0
  37. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/fileloader.py +0 -0
  38. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/helper.py +0 -0
  39. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/ipython_display.py +0 -0
  40. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/legend.py +0 -0
  41. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/line.py +0 -0
  42. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/marker.py +0 -0
  43. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/palette.py +0 -0
  44. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/profiler.py +0 -0
  45. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/progress.py +0 -0
  46. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/round.py +0 -0
  47. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/shapes.py +0 -0
  48. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/styles.py +0 -0
  49. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/svg.py +0 -0
  50. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/svg_pdf.py +0 -0
  51. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/symbol.py +0 -0
  52. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/text.py +0 -0
  53. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/thin.py +0 -0
  54. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/core/window.py +0 -0
  55. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/data/__init__.py +0 -0
  56. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/data/excel.py +0 -0
  57. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/__init__.py +0 -0
  58. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/_lazy.py +0 -0
  59. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d2/__init__.py +0 -0
  60. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d2/arrow.py +0 -0
  61. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d2/bubble.py +0 -0
  62. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d2/contour.py +0 -0
  63. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d2/equation.py +0 -0
  64. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d2/fill.py +0 -0
  65. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d2/function.py +0 -0
  66. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d2/guide_grid.py +0 -0
  67. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d2/inequality.py +0 -0
  68. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d2/map.py +0 -0
  69. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d2/parameter.py +0 -0
  70. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d2/pillar.py +0 -0
  71. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d2/point.py +0 -0
  72. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d3/__init__.py +0 -0
  73. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d3/base.py +0 -0
  74. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d3/function.py +0 -0
  75. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d3/mesh.py +0 -0
  76. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d3/point.py +0 -0
  77. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/d3/potato.py +0 -0
  78. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/function.py +0 -0
  79. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/legend.py +0 -0
  80. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/mapdata.py +0 -0
  81. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/point.py +0 -0
  82. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/objects/text.py +0 -0
  83. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/__init__.py +0 -0
  84. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/_lazy.py +0 -0
  85. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/box.py +0 -0
  86. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/constants.py +0 -0
  87. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/d3/__init__.py +0 -0
  88. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/d3/axes.py +0 -0
  89. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/d3/geometry.py +0 -0
  90. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/d3/plot3d.py +0 -0
  91. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/d3/variants.py +0 -0
  92. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/double.py +0 -0
  93. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/empty.py +0 -0
  94. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/grid.py +0 -0
  95. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/log.py +0 -0
  96. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/polar.py +0 -0
  97. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/standard.py +0 -0
  98. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/themes.py +0 -0
  99. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/zoom.py +0 -0
  100. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/plot/zoom_connector.py +0 -0
  101. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/project/__init__.py +0 -0
  102. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/project/codec.py +0 -0
  103. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/project/context.py +0 -0
  104. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/project/document.py +0 -0
  105. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/project/registry.py +0 -0
  106. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/project/sample.py +0 -0
  107. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/project/sampled_curve.py +0 -0
  108. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/project/serializers.py +0 -0
  109. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/project/window.py +0 -0
  110. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/__init__.py +0 -0
  111. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/__init__.py +0 -0
  112. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.bright-oblique.ttf +0 -0
  113. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.bright-roman.ttf +0 -0
  114. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.bright-semibold.ttf +0 -0
  115. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.bright-semiboldoblique.ttf +0 -0
  116. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.classical-serif-italic.ttf +0 -0
  117. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.concrete-bold.ttf +0 -0
  118. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.concrete-bolditalic.ttf +0 -0
  119. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.concrete-italic.ttf +0 -0
  120. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.concrete-roman.ttf +0 -0
  121. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-bold.ttf +0 -0
  122. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-boldoblique.ttf +0 -0
  123. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-demi-condensed-demicondensed.ttf +0 -0
  124. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-medium.ttf +0 -0
  125. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-oblique.ttf +0 -0
  126. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.serif-bold.ttf +0 -0
  127. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.serif-bolditalic.ttf +0 -0
  128. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-boldslanted.ttf +0 -0
  129. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-romanslanted.ttf +0 -0
  130. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.serif-italic.ttf +0 -0
  131. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.serif-roman.ttf +0 -0
  132. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.serif-upright-italic-uprightitalic.ttf +0 -0
  133. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bold.ttf +0 -0
  134. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bolditalic.ttf +0 -0
  135. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-italic.ttf +0 -0
  136. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-light.ttf +0 -0
  137. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-lightoblique.ttf +0 -0
  138. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-regular.ttf +0 -0
  139. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-italic.ttf +0 -0
  140. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-medium.ttf +0 -0
  141. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/computer-modern-family/readme.txt +0 -0
  142. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/resources/logo-small.png +0 -0
  143. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/viewer/__init__.py +0 -0
  144. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/viewer/cli.py +0 -0
  145. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/viewer/client.py +0 -0
  146. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe/viewer/server.py +0 -0
  147. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe.egg-info/SOURCES.txt +0 -0
  148. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe.egg-info/dependency_links.txt +0 -0
  149. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe.egg-info/entry_points.txt +0 -0
  150. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe.egg-info/requires.txt +0 -0
  151. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/src/kaxe.egg-info/top_level.txt +0 -0
  152. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/tests/test.py +0 -0
  153. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/tests/test2.py +0 -0
  154. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/tests/test3.py +0 -0
  155. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/tests/test_4.py +0 -0
  156. {kaxe-1.8.1.dev4 → kaxe-1.8.1.dev6}/tests/test_5.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kaxe
3
- Version: 1.8.1.dev4
3
+ Version: 1.8.1.dev6
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
@@ -87,7 +87,9 @@ When the viewer is open, `.show()` instantly streams plots to cards on your canv
87
87
  **Canvas & Card Controls:**
88
88
  * **Click & Drag on a Card**: Move that card freely across the canvas
89
89
  * **Click & Drag on Empty Space**: Pan the camera across the workspace
90
- * **Mouse Scroll**: Smooth zoom anchored to cursor
90
+ * **Two-Finger Scroll / Trackpad**: Pan canvas side-to-side and up/down without zooming
91
+ * **`[Shift]` + Scroll**: Pan horizontally (for 1D mouse wheels)
92
+ * **`[Cmd]` / `[Ctrl]` + Scroll**: Smooth zoom anchored to cursor
91
93
  * **`[A]`**: Auto-arrange all cards in a neat row
92
94
  * **`[Space]` or `[R]`**: Fit all cards on the canvas into view
93
95
  * **`[1]`**: View canvas at 100% native pixel scale
@@ -54,7 +54,9 @@ When the viewer is open, `.show()` instantly streams plots to cards on your canv
54
54
  **Canvas & Card Controls:**
55
55
  * **Click & Drag on a Card**: Move that card freely across the canvas
56
56
  * **Click & Drag on Empty Space**: Pan the camera across the workspace
57
- * **Mouse Scroll**: Smooth zoom anchored to cursor
57
+ * **Two-Finger Scroll / Trackpad**: Pan canvas side-to-side and up/down without zooming
58
+ * **`[Shift]` + Scroll**: Pan horizontally (for 1D mouse wheels)
59
+ * **`[Cmd]` / `[Ctrl]` + Scroll**: Smooth zoom anchored to cursor
58
60
  * **`[A]`**: Auto-arrange all cards in a neat row
59
61
  * **`[Space]` or `[R]`**: Fit all cards on the canvas into view
60
62
  * **`[1]`**: View canvas at 100% native pixel scale
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kaxe"
3
- version = "1.8.1.dev4"
3
+ version = "1.8.1.dev6"
4
4
  authors = [
5
5
  { name="Valter Yde Daugberg", email="valteryde@hotmail.com" },
6
6
  ]
@@ -268,7 +268,19 @@ class KaxeViewerWindow(pyglet.window.Window):
268
268
 
269
269
  # Mouse interaction state
270
270
  self.dragging_card: Optional[PlotCard] = None
271
+ self.drag_offset_x: float = 0.0
272
+ self.drag_offset_y: float = 0.0
273
+
271
274
  self.is_panning_camera: bool = False
275
+ self.pan_start_x: float = 0.0
276
+ self.pan_start_y: float = 0.0
277
+ self.pan_camera_start_x: float = 0.0
278
+ self.pan_camera_start_y: float = 0.0
279
+
280
+ # Key state handler for live modifier tracking (Cmd, Ctrl, Shift)
281
+ self.keys = key.KeyStateHandler()
282
+ self.push_handlers(self.keys)
283
+ self.current_modifiers: int = 0
272
284
 
273
285
  self.show_hud: bool = True
274
286
  self.notification_text: str = ""
@@ -300,7 +312,7 @@ class KaxeViewerWindow(pyglet.window.Window):
300
312
  )
301
313
 
302
314
  self.hud_right = pyglet.text.Label(
303
- "[Drag] Move card / Pan [Scroll] Zoom [A] Arrange [←/→] Version [C] Copy [Space] Fit",
315
+ "[Drag] Move card / Pan [Cmd+Scroll] Zoom [Scroll] Pan [A] Arrange [Space] Fit",
304
316
  font_size=11,
305
317
  x=self.width - 15,
306
318
  y=15,
@@ -455,38 +467,117 @@ class KaxeViewerWindow(pyglet.window.Window):
455
467
  self.cards.append(hit_card)
456
468
  self.select_card(hit_card)
457
469
  self.dragging_card = hit_card
470
+ # Anchor grab offset so dragging sticks directly to the cursor
471
+ self.drag_offset_x = wx - hit_card.world_x
472
+ self.drag_offset_y = wy - hit_card.world_y
458
473
  else:
459
474
  # Clicked empty canvas -> pan
460
475
  self.select_card(None)
461
476
  self.is_panning_camera = True
477
+ self.pan_start_x = x
478
+ self.pan_start_y = y
479
+ self.pan_camera_start_x = self.camera_x
480
+ self.pan_camera_start_y = self.camera_y
462
481
 
463
482
  def on_mouse_drag(self, x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int):
464
483
  if self.dragging_card:
465
- # Move card in world space
466
- self.dragging_card.world_x += dx / self.zoom
467
- self.dragging_card.world_y += dy / self.zoom
484
+ # Absolute position tracking: card sticks precisely to the cursor
485
+ wx, wy = self.screen_to_world(x, y)
486
+ self.dragging_card.world_x = wx - self.drag_offset_x
487
+ self.dragging_card.world_y = wy - self.drag_offset_y
468
488
  elif self.is_panning_camera:
469
- # Move camera in world space
470
- self.camera_x -= dx / self.zoom
471
- self.camera_y -= dy / self.zoom
489
+ # Absolute canvas panning: workspace follows the mouse 1:1
490
+ self.camera_x = self.pan_camera_start_x - (x - self.pan_start_x) / self.zoom
491
+ self.camera_y = self.pan_camera_start_y - (y - self.pan_start_y) / self.zoom
472
492
 
473
493
  def on_mouse_release(self, x: int, y: int, button: int, modifiers: int):
474
494
  self.dragging_card = None
475
495
  self.is_panning_camera = False
476
496
 
497
+ def is_zoom_modifier_pressed(self) -> bool:
498
+ """Return True if Meta (Command) or Control key is held down."""
499
+ if sys.platform == "darwin":
500
+ try:
501
+ from pyglet.libs.darwin import cocoapy
502
+ flags = cocoapy.NSEvent.modifierFlags()
503
+ if flags & (cocoapy.NSCommandKeyMask | cocoapy.NSControlKeyMask):
504
+ return True
505
+ except Exception:
506
+ pass
507
+
508
+ if hasattr(self, "keys") and self.keys is not None:
509
+ if (
510
+ self.keys[key.LCOMMAND]
511
+ or self.keys[key.RCOMMAND]
512
+ or self.keys[key.LCTRL]
513
+ or self.keys[key.RCTRL]
514
+ ):
515
+ return True
516
+
517
+ if getattr(self, "current_modifiers", 0) & (key.MOD_COMMAND | key.MOD_CTRL):
518
+ return True
519
+
520
+ return False
521
+
522
+ def is_shift_pressed(self) -> bool:
523
+ """Return True if Shift key is held down."""
524
+ if sys.platform == "darwin":
525
+ try:
526
+ from pyglet.libs.darwin import cocoapy
527
+ flags = cocoapy.NSEvent.modifierFlags()
528
+ if flags & cocoapy.NSShiftKeyMask:
529
+ return True
530
+ except Exception:
531
+ pass
532
+
533
+ if hasattr(self, "keys") and self.keys is not None:
534
+ if self.keys[key.LSHIFT] or self.keys[key.RSHIFT]:
535
+ return True
536
+
537
+ if getattr(self, "current_modifiers", 0) & key.MOD_SHIFT:
538
+ return True
539
+
540
+ return False
541
+
477
542
  def on_mouse_scroll(self, x: int, y: int, scroll_x: float, scroll_y: float):
478
- """Cursor-anchored zooming on the canvas."""
479
- zoom_factor = 1.05 ** (-scroll_y)
480
- new_zoom = max(0.04, min(self.zoom * zoom_factor, 20.0))
543
+ """Scroll handler:
481
544
 
482
- if hasattr(self, "width"):
483
- wx, wy = self.screen_to_world(x, y)
484
- self.camera_x = wx - (x - self.width / 2.0) / new_zoom
485
- self.camera_y = wy - (y - self.height / 2.0) / new_zoom
545
+ - If Meta (Cmd) or Control is held: ZOOM anchored at cursor.
546
+ - Otherwise: PAN canvas side-to-side and up/down without zooming.
547
+ """
548
+ if hasattr(self, "keys") and self.keys is not None:
549
+ should_zoom = self.is_zoom_modifier_pressed()
550
+ else:
551
+ # Fallback for mock unit test instances
552
+ should_zoom = True
486
553
 
487
- self.zoom = new_zoom
554
+ if should_zoom:
555
+ zoom_factor = 1.05 ** (-scroll_y)
556
+ new_zoom = max(0.04, min(self.zoom * zoom_factor, 25.0))
557
+
558
+ if hasattr(self, "width"):
559
+ wx, wy = self.screen_to_world(x, y)
560
+ self.camera_x = wx - (x - self.width / 2.0) / new_zoom
561
+ self.camera_y = wy - (y - self.height / 2.0) / new_zoom
562
+
563
+ self.zoom = new_zoom
564
+ else:
565
+ # Pan without zooming
566
+ dx = scroll_x
567
+ dy = scroll_y
568
+
569
+ # Holding Shift converts vertical wheel into horizontal pan
570
+ if self.is_shift_pressed() and dy != 0.0 and dx == 0.0:
571
+ dx = dy
572
+ dy = 0.0
573
+
574
+ pan_speed = 20.0
575
+ self.camera_x -= (dx * pan_speed) / self.zoom
576
+ self.camera_y += (dy * pan_speed) / self.zoom
488
577
 
489
578
  def on_key_press(self, symbol: int, modifiers: int):
579
+ self.current_modifiers = modifiers
580
+
490
581
  # Fit all
491
582
  if symbol in (key.R, key.SPACE):
492
583
  self.fit_all_cards()
@@ -522,6 +613,9 @@ class KaxeViewerWindow(pyglet.window.Window):
522
613
  elif symbol == key.S:
523
614
  self.save_selected_card()
524
615
 
616
+ def on_key_release(self, symbol: int, modifiers: int):
617
+ self.current_modifiers = modifiers
618
+
525
619
  def copy_selected_card(self):
526
620
  if not self.selected_card or not self.selected_card.current_version:
527
621
  return
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kaxe
3
- Version: 1.8.1.dev4
3
+ Version: 1.8.1.dev6
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
@@ -87,7 +87,9 @@ When the viewer is open, `.show()` instantly streams plots to cards on your canv
87
87
  **Canvas & Card Controls:**
88
88
  * **Click & Drag on a Card**: Move that card freely across the canvas
89
89
  * **Click & Drag on Empty Space**: Pan the camera across the workspace
90
- * **Mouse Scroll**: Smooth zoom anchored to cursor
90
+ * **Two-Finger Scroll / Trackpad**: Pan canvas side-to-side and up/down without zooming
91
+ * **`[Shift]` + Scroll**: Pan horizontally (for 1D mouse wheels)
92
+ * **`[Cmd]` / `[Ctrl]` + Scroll**: Smooth zoom anchored to cursor
91
93
  * **`[A]`**: Auto-arrange all cards in a neat row
92
94
  * **`[Space]` or `[R]`**: Fit all cards on the canvas into view
93
95
  * **`[1]`**: View canvas at 100% native pixel scale
@@ -220,6 +220,36 @@ def test_viewer_zoom_reversed_and_less_sensitive():
220
220
  assert abs(window.zoom - 1.05) < 1e-6
221
221
 
222
222
 
223
+ def test_scroll_pans_side_to_side_without_zoom_modifier():
224
+ from unittest.mock import MagicMock
225
+ from kaxe.viewer.window import KaxeViewerWindow
226
+ from pyglet.window import key
227
+
228
+ window = object.__new__(KaxeViewerWindow)
229
+ window.keys = {key.LCOMMAND: False, key.RCOMMAND: False, key.LCTRL: False, key.RCTRL: False, key.LSHIFT: False, key.RSHIFT: False}
230
+ window.current_modifiers = 0
231
+ window.camera_x = 0.0
232
+ window.camera_y = 0.0
233
+ window.zoom = 1.0
234
+
235
+ # Horizontal scroll without Cmd/Ctrl -> pans horizontally, zoom stays unchanged!
236
+ window.on_mouse_scroll(x=400, y=300, scroll_x=2.0, scroll_y=0.0)
237
+ assert window.zoom == 1.0
238
+ assert window.camera_x < 0.0 # Camera moved left
239
+
240
+ # Vertical scroll without Cmd/Ctrl -> pans vertically, zoom stays unchanged!
241
+ orig_cam_x = window.camera_x
242
+ window.on_mouse_scroll(x=400, y=300, scroll_x=0.0, scroll_y=3.0)
243
+ assert window.zoom == 1.0
244
+ assert window.camera_x == orig_cam_x
245
+ assert window.camera_y > 0.0
246
+
247
+ # With Cmd key pressed -> zooms!
248
+ window.keys[key.LCOMMAND] = True
249
+ window.on_mouse_scroll(x=400, y=300, scroll_x=0.0, scroll_y=-1.0)
250
+ assert window.zoom > 1.0
251
+
252
+
223
253
  def test_plot_id_chaining_and_streaming(monkeypatch):
224
254
  import kaxe
225
255
 
@@ -282,11 +312,13 @@ def test_canvas_multi_card_and_versioning():
282
312
  assert window.cards[0].card_id == "graph_1"
283
313
  assert window.cards[1].card_id == "graph_2"
284
314
 
285
- # 4. Moving card changes its coordinates
286
- orig_x = window.cards[1].world_x
287
- window.dragging_card = window.cards[1]
288
- window.on_mouse_drag(x=100, y=100, dx=30, dy=15, buttons=1, modifiers=0)
289
- assert window.cards[1].world_x > orig_x
315
+ # 4. Moving card changes its coordinates via press and drag
316
+ card = window.cards[1]
317
+ sx, sy = window.world_to_screen(card.world_x + 20, card.world_y + 20)
318
+ window.on_mouse_press(int(sx), int(sy), button=1, modifiers=0)
319
+ orig_x = card.world_x
320
+ window.on_mouse_drag(int(sx + 50), int(sy + 20), dx=50, dy=20, buttons=1, modifiers=0)
321
+ assert card.world_x > orig_x
290
322
 
291
323
  window.close()
292
324
 
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