kaxe 1.7.0.dev0__tar.gz → 1.7.1__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.7.0.dev0/src/kaxe.egg-info → kaxe-1.7.1}/PKG-INFO +1 -1
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/pyproject.toml +1 -1
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/__init__.py +1 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/chart/box.py +113 -4
- kaxe-1.7.1/src/kaxe/core/thin.py +255 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d2/inequality.py +2 -18
- {kaxe-1.7.0.dev0 → kaxe-1.7.1/src/kaxe.egg-info}/PKG-INFO +1 -1
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe.egg-info/SOURCES.txt +1 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/LICENSE +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/MANIFEST.in +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/README.md +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/setup.cfg +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/_require_3d.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/chart/__init__.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/chart/bar.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/chart/pie.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/chart/qqplot.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/__init__.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/axis.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/bounds.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/color.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/d3/backend.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/d3/camera.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/d3/helper.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/d3/hud.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/d3/objects/__init__.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/d3/objects/color.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/d3/objects/line.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/d3/objects/point.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/d3/objects/pointer.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/d3/objects/triangle.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/d3/openglrender.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/d3/translator.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/draw.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/fileloader.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/helper.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/ipython_display.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/legend.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/line.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/marker.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/palette.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/profiler.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/progress.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/round.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/shapes.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/styles.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/svg.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/svg_pdf.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/symbol.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/text.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/core/window.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/data/__init__.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/data/excel.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/__init__.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/_lazy.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d2/__init__.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d2/arrow.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d2/bubble.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d2/contour.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d2/equation.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d2/fill.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d2/function.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d2/guide_grid.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d2/map.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d2/parameter.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d2/pillar.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d2/point.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d3/__init__.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d3/base.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d3/function.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d3/mesh.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d3/point.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/d3/potato.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/function.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/legend.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/mapdata.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/point.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/objects/text.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/__init__.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/_lazy.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/box.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/constants.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/d3/__init__.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/d3/axes.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/d3/geometry.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/d3/plot3d.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/d3/variants.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/double.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/empty.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/grid.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/log.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/polar.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/standard.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/themes.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/zoom.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/plot/zoom_connector.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/project/__init__.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/project/codec.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/project/context.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/project/document.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/project/registry.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/project/sample.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/project/sampled_curve.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/project/serializers.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/project/window.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/__init__.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/__init__.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.bright-oblique.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.bright-roman.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.bright-semibold.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.bright-semiboldoblique.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.classical-serif-italic.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.concrete-bold.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.concrete-bolditalic.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.concrete-italic.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.concrete-roman.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-bold.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-boldoblique.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-demi-condensed-demicondensed.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-medium.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-oblique.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.serif-bold.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.serif-bolditalic.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-boldslanted.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.serif-extra-romanslanted.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.serif-italic.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.serif-roman.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.serif-upright-italic-uprightitalic.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bold.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-bolditalic.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-italic.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-light.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-lightoblique.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-regular.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-italic.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.typewriter-text-variable-width-medium.ttf +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/readme.txt +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/logo-small.png +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe.egg-info/dependency_links.txt +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe.egg-info/requires.txt +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe.egg-info/top_level.txt +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/tests/test.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/tests/test2.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/tests/test3.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/tests/test_4.py +0 -0
- {kaxe-1.7.0.dev0 → kaxe-1.7.1}/tests/test_5.py +0 -0
|
@@ -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
|
|
193
|
+
for value in data:
|
|
155
194
|
# Outliers: points outside the whisker range (beyond fence)
|
|
156
|
-
if
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
{kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.bright-oblique.ttf
RENAMED
|
File without changes
|
{kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.bright-roman.ttf
RENAMED
|
File without changes
|
{kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.bright-semibold.ttf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.concrete-bold.ttf
RENAMED
|
File without changes
|
{kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.concrete-bolditalic.ttf
RENAMED
|
File without changes
|
{kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.concrete-italic.ttf
RENAMED
|
File without changes
|
{kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.concrete-roman.ttf
RENAMED
|
File without changes
|
{kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-bold.ttf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-medium.ttf
RENAMED
|
File without changes
|
{kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.sans-serif-oblique.ttf
RENAMED
|
File without changes
|
|
File without changes
|
{kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.serif-bolditalic.ttf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.serif-italic.ttf
RENAMED
|
File without changes
|
{kaxe-1.7.0.dev0 → kaxe-1.7.1}/src/kaxe/resources/computer-modern-family/cmu.serif-roman.ttf
RENAMED
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|