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.
- {kaxe-1.5.2.dev1/src/kaxe.egg-info → kaxe-1.5.5}/PKG-INFO +1 -1
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/pyproject.toml +1 -1
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/helper.py +68 -0
- kaxe-1.5.5/src/kaxe/core/ipython_display.py +32 -0
- kaxe-1.5.5/src/kaxe/core/progress.py +47 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/window.py +37 -37
- kaxe-1.5.5/src/kaxe/objects/d2/contour.py +305 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/equation.py +95 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/inequality.py +16 -8
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/point.py +57 -2
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/point.py +2 -1
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/d3/plot3d.py +4 -4
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/grid.py +13 -15
- {kaxe-1.5.2.dev1 → kaxe-1.5.5/src/kaxe.egg-info}/PKG-INFO +1 -1
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe.egg-info/SOURCES.txt +2 -0
- kaxe-1.5.2.dev1/src/kaxe/objects/d2/contour.py +0 -113
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/LICENSE +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/MANIFEST.in +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/README.md +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/setup.cfg +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/__init__.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/_require_3d.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/chart/__init__.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/chart/bar.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/chart/box.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/chart/pie.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/chart/qqplot.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/__init__.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/axis.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/bounds.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/color.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/backend.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/camera.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/helper.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/hud.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/objects/__init__.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/objects/color.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/objects/line.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/objects/point.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/objects/pointer.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/objects/triangle.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/openglrender.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/d3/translator.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/draw.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/fileloader.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/legend.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/line.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/marker.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/profiler.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/round.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/shapes.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/styles.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/svg.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/svg_pdf.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/symbol.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/core/text.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/data/__init__.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/data/excel.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/__init__.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/_lazy.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/__init__.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/arrow.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/bubble.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/fill.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/function.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/map.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/parameter.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d2/pillar.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d3/__init__.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d3/base.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d3/function.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d3/mesh.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d3/point.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/d3/potato.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/function.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/legend.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/mapdata.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/objects/text.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/__init__.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/_lazy.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/box.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/constants.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/d3/__init__.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/d3/axes.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/d3/geometry.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/d3/variants.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/double.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/empty.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/log.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/polar.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/standard.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/themes.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/zoom.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/plot/zoom_connector.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/__init__.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/codec.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/context.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/document.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/registry.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/sample.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/sampled_curve.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/serializers.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/project/window.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/__init__.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/__init__.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.bright-oblique.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.bright-roman.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.bright-semibold.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.bright-semiboldoblique.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.classical-serif-italic.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.concrete-bold.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.concrete-bolditalic.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.concrete-italic.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.concrete-roman.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-bold.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-boldoblique.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-demi-condensed-demicondensed.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-medium.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-oblique.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-bold.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-bolditalic.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-boldslanted.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-romanslanted.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-italic.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-roman.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.serif-upright-italic-uprightitalic.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bold.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bolditalic.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-italic.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-light.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-lightoblique.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-regular.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-italic.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-medium.ttf +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/computer-modern-family/readme.txt +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/logo-small.png +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/symbolcross.png +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/symboldonut.png +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/symbollollipop.png +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe/resources/symboltriangle.png +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe.egg-info/dependency_links.txt +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe.egg-info/requires.txt +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/src/kaxe.egg-info/top_level.txt +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/tests/test.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/tests/test2.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/tests/test3.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/tests/test_4.py +0 -0
- {kaxe-1.5.2.dev1 → kaxe-1.5.5}/tests/test_5.py +0 -0
|
@@ -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 = {
|
|
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
|
|
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
|
-
|
|
494
|
+
pbar = make_progress_bar(self.showProgressBar, len(self.objects), 'Baking')
|
|
491
495
|
for obj in self.objects:
|
|
492
496
|
self.__callFinalizeObject__(obj)
|
|
493
|
-
|
|
497
|
+
pbar.update()
|
|
494
498
|
self.addDrawingFunction(obj)
|
|
495
|
-
|
|
499
|
+
pbar.close()
|
|
496
500
|
|
|
497
501
|
|
|
498
502
|
def __pillowPaint__(self, fname=None):
|
|
499
503
|
startTime = time.time()
|
|
500
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
572
|
+
pbar.update()
|
|
570
573
|
|
|
571
574
|
pdf_bytes = doc.to_pdf(fname)
|
|
572
575
|
|
|
573
|
-
|
|
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
|
-
|
|
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
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
-
|
|
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
|