plot-io 0.12.0__py3-none-win_amd64.whl
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.
- plot_io/__init__.py +73 -0
- plot_io/_methods.py +1691 -0
- plot_io/_native/__init__.py +199 -0
- plot_io/_native/__main__.py +197 -0
- plot_io/_native/_api.py +457 -0
- plot_io/_native/bin/windows-x86_64.dll +0 -0
- plot_io/_native/kernels.py +335 -0
- plot_io/_native/manifest.json +49 -0
- plot_io/axis.py +1347 -0
- plot_io/boxes.py +1156 -0
- plot_io/canvas.py +389 -0
- plot_io/color/__init__.py +3 -0
- plot_io/color/data.py +1646 -0
- plot_io/color/maps.py +922 -0
- plot_io/color/values.py +295 -0
- plot_io/color/variants.py +1246 -0
- plot_io/colorbar.py +1426 -0
- plot_io/columns.py +614 -0
- plot_io/decorations.py +920 -0
- plot_io/defaults.py +809 -0
- plot_io/density.py +113 -0
- plot_io/ext.py +68 -0
- plot_io/facets.py +240 -0
- plot_io/figure.py +788 -0
- plot_io/formatters.py +97 -0
- plot_io/genome/__init__.py +17 -0
- plot_io/genome/adapters.py +356 -0
- plot_io/genome/combine.py +123 -0
- plot_io/genome/contacts.py +508 -0
- plot_io/genome/intervals.py +750 -0
- plot_io/genome/lanes.py +191 -0
- plot_io/genome/layout.py +184 -0
- plot_io/genome/links.py +364 -0
- plot_io/genome/pack.py +27 -0
- plot_io/genome/readers.py +145 -0
- plot_io/genome/regions.py +228 -0
- plot_io/genome/scale.py +349 -0
- plot_io/genome/series.py +344 -0
- plot_io/genome/signal.py +621 -0
- plot_io/genome/sites.py +782 -0
- plot_io/genome/sources.py +459 -0
- plot_io/genome/testing.py +161 -0
- plot_io/geometry.py +289 -0
- plot_io/groups.py +308 -0
- plot_io/guides.py +503 -0
- plot_io/helpers/__init__.py +17 -0
- plot_io/helpers/clustermap.py +202 -0
- plot_io/helpers/dendrogram.py +172 -0
- plot_io/helpers/grids.py +304 -0
- plot_io/helpers/qq.py +334 -0
- plot_io/helpers/raincloud.py +249 -0
- plot_io/helpers/tree.py +214 -0
- plot_io/helpers/volcano.py +505 -0
- plot_io/hints.py +125 -0
- plot_io/kernels.py +740 -0
- plot_io/labels.py +566 -0
- plot_io/layers/__init__.py +100 -0
- plot_io/layers/base.py +630 -0
- plot_io/layers/context.py +87 -0
- plot_io/layers/core.py +446 -0
- plot_io/layers/geoms.py +1362 -0
- plot_io/layers/grammar.py +586 -0
- plot_io/layers/paint.py +614 -0
- plot_io/layers/pipeline.py +170 -0
- plot_io/layers/positions.py +431 -0
- plot_io/layers/stats.py +1088 -0
- plot_io/layout.py +1381 -0
- plot_io/legend.py +605 -0
- plot_io/levels.py +509 -0
- plot_io/limits.py +124 -0
- plot_io/lines.py +42 -0
- plot_io/mapped.py +522 -0
- plot_io/markers.py +110 -0
- plot_io/markup.py +128 -0
- plot_io/numbers.py +354 -0
- plot_io/occupancy.py +295 -0
- plot_io/plot.py +597 -0
- plot_io/presets/__init__.py +51 -0
- plot_io/presets/_util.py +153 -0
- plot_io/presets/area.py +145 -0
- plot_io/presets/bar.py +261 -0
- plot_io/presets/box.py +400 -0
- plot_io/presets/catpoints.py +177 -0
- plot_io/presets/contour.py +353 -0
- plot_io/presets/curves.py +30 -0
- plot_io/presets/dots.py +355 -0
- plot_io/presets/ecdf.py +97 -0
- plot_io/presets/errorbar.py +106 -0
- plot_io/presets/errors.py +296 -0
- plot_io/presets/fill.py +74 -0
- plot_io/presets/heatmap.py +322 -0
- plot_io/presets/hexbin.py +198 -0
- plot_io/presets/hist.py +391 -0
- plot_io/presets/hist2d.py +87 -0
- plot_io/presets/kde.py +162 -0
- plot_io/presets/kde2d.py +147 -0
- plot_io/presets/line.py +143 -0
- plot_io/presets/pie.py +502 -0
- plot_io/presets/regression.py +115 -0
- plot_io/presets/scatter.py +194 -0
- plot_io/presets/stem.py +100 -0
- plot_io/presets/violin.py +323 -0
- plot_io/py.typed +0 -0
- plot_io/registry.py +82 -0
- plot_io/render.py +480 -0
- plot_io/rows.py +491 -0
- plot_io/scales.py +103 -0
- plot_io/seams.py +138 -0
- plot_io/settings.py +807 -0
- plot_io/shapes.py +66 -0
- plot_io/sharing.py +232 -0
- plot_io/sizemaps.py +97 -0
- plot_io/spec.py +240 -0
- plot_io/states.py +216 -0
- plot_io/styles/__init__.py +307 -0
- plot_io/styles/library.py +397 -0
- plot_io/tables.py +138 -0
- plot_io/text.py +412 -0
- plot_io/ticks.py +400 -0
- plot_io/times.py +287 -0
- plot_io/titles.py +225 -0
- plot_io/transform.py +114 -0
- plot_io/types.py +13604 -0
- plot_io/units.py +768 -0
- plot_io/validators.py +1029 -0
- plot_io/values.py +190 -0
- plot_io-0.12.0.dist-info/METADATA +215 -0
- plot_io-0.12.0.dist-info/RECORD +130 -0
- plot_io-0.12.0.dist-info/WHEEL +4 -0
- plot_io-0.12.0.dist-info/licenses/LICENSE +28 -0
plot_io/seams.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Seams: where outlined shapes touch (stacked, dodged or adjacent bars, pie wedges), the
|
|
2
|
+
shared edge is redrawn in one color (white by default), so neighbours stay apart instead of
|
|
3
|
+
showing two overlapping outlines. The outlines under a seam are left out of the shapes'
|
|
4
|
+
geometry (not painted over), so none shows through the seam's anti-aliased edges."""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
from .canvas import Canvas
|
|
8
|
+
|
|
9
|
+
__all__ = ["rect_contacts", "rect_seams", "seam_box", "outline_pieces", "draw_seams"]
|
|
10
|
+
|
|
11
|
+
Rect = tuple[float, float, float, float, float] # x0, y0, x1, y1 (page units), width
|
|
12
|
+
_EPS = 0.01 # page units: edges this close touch
|
|
13
|
+
#: (at most: closer for rectangles smaller than 1 px, see :func:`rect_contacts`)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def rect_seams(new: list[Rect], old: list[Rect]) -> list[tuple[float, ...]]:
|
|
17
|
+
"""Segments ``(x0, y0, x1, y1, width)`` where a rectangle of ``new`` touches another of
|
|
18
|
+
``new`` or one of ``old`` (edges at the same place, overlapping along it); the width is
|
|
19
|
+
the wider outline of the two."""
|
|
20
|
+
return list(dict.fromkeys(s for s, _ in rect_contacts(new, old)))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def rect_contacts(new: list[Rect], old: list[Rect]) -> list[tuple[tuple[float, ...], int]]:
|
|
24
|
+
"""As :func:`rect_seams`, each segment with the index of the other rectangle of the
|
|
25
|
+
pair in ``old + new`` (an earlier one when it is in ``old``)."""
|
|
26
|
+
import bisect
|
|
27
|
+
out: list[tuple[tuple[float, ...], int]] = []
|
|
28
|
+
every = old + new
|
|
29
|
+
# per side (left, right, top, bottom: the coordinate's index in a rectangle), the
|
|
30
|
+
# rectangles by the position of that edge in steps of _EPS; each bucket sorted by the
|
|
31
|
+
# start along the edge (y0 for left and right, x0 for top and bottom), with the
|
|
32
|
+
# starts and the longest edge for bisection
|
|
33
|
+
index: list[dict[int, Any]] = [{}, {}, {}, {}]
|
|
34
|
+
for i, r in enumerate(every):
|
|
35
|
+
for c in range(4):
|
|
36
|
+
index[c].setdefault(round(r[c] / _EPS), []).append(i)
|
|
37
|
+
for c in range(4):
|
|
38
|
+
a = 1 if c in (0, 2) else 0
|
|
39
|
+
for k, js in index[c].items():
|
|
40
|
+
js.sort(key=lambda j: every[j][a])
|
|
41
|
+
longest = max(0.0, *(every[j][a + 2] - every[j][a] for j in js))
|
|
42
|
+
index[c][k] = (js, [every[j][a] for j in js], longest)
|
|
43
|
+
first_new = len(old)
|
|
44
|
+
size = [min(r[2] - r[0], r[3] - r[1]) for r in every]
|
|
45
|
+
# right and bottom edges against every other rectangle; left and top ones against the
|
|
46
|
+
# earlier rectangles only (pairs of new ones are found from one side)
|
|
47
|
+
sides = ((2, 0, True), (3, 1, True), (0, 2, False), (1, 3, False))
|
|
48
|
+
for i in range(first_new, len(every)):
|
|
49
|
+
r = every[i]
|
|
50
|
+
x0, y0, x1, y1, w = r
|
|
51
|
+
for mine, other, both in sides:
|
|
52
|
+
v = r[mine]
|
|
53
|
+
k = round(v / _EPS)
|
|
54
|
+
found = index[other]
|
|
55
|
+
lo_e, hi_e = (y0, y1) if mine in (0, 2) else (x0, x1)
|
|
56
|
+
near: list[int] = []
|
|
57
|
+
for kk in (k - 1, k, k + 1):
|
|
58
|
+
bucket = found.get(kk)
|
|
59
|
+
if bucket is None:
|
|
60
|
+
continue
|
|
61
|
+
js, starts, longest = bucket
|
|
62
|
+
first = bisect.bisect_left(starts, lo_e - longest)
|
|
63
|
+
last = bisect.bisect_right(starts, hi_e)
|
|
64
|
+
near.extend(sorted(js[first:last]))
|
|
65
|
+
for j in near:
|
|
66
|
+
if j == i or (not both and j >= first_new):
|
|
67
|
+
continue
|
|
68
|
+
b = every[j]
|
|
69
|
+
# edges this close touch: _EPS, or 1 % of the smaller rectangle (gaps
|
|
70
|
+
# between rectangles narrower than a px are not contacts)
|
|
71
|
+
if abs(b[other] - v) > min(_EPS, 0.01 * min(size[i], size[j])):
|
|
72
|
+
continue
|
|
73
|
+
width = max(w, b[4])
|
|
74
|
+
if mine in (0, 2):
|
|
75
|
+
lo, hi = max(y0, b[1]), min(y1, b[3])
|
|
76
|
+
if hi - lo > _EPS:
|
|
77
|
+
out.append(((v, lo, v, hi, width), j))
|
|
78
|
+
else:
|
|
79
|
+
lo, hi = max(x0, b[0]), min(x1, b[2])
|
|
80
|
+
if hi - lo > _EPS:
|
|
81
|
+
out.append(((lo, v, hi, v, width), j))
|
|
82
|
+
return out
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
Box = tuple[float, float, float, float] # x0, y0, x1, y1
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def seam_box(segment: tuple[float, ...], square: bool = False) -> Box:
|
|
89
|
+
"""The area an axis-aligned segment ``(x0, y0, x1, y1, width)`` covers when stroked
|
|
90
|
+
with butt caps, or square caps (``square``)."""
|
|
91
|
+
x0, y0, x1, y1, w = segment[:5]
|
|
92
|
+
h = w / 2
|
|
93
|
+
e = h if square else 0.0
|
|
94
|
+
if abs(x1 - x0) <= _EPS:
|
|
95
|
+
return (x0 - h, min(y0, y1) - e, x0 + h, max(y0, y1) + e)
|
|
96
|
+
return (min(x0, x1) - e, y0 - h, max(x0, x1) + e, y0 + h)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _minus(a: Box, b: Box) -> list[Box]:
|
|
100
|
+
ax0, ay0, ax1, ay1 = a
|
|
101
|
+
bx0, by0, bx1, by1 = b
|
|
102
|
+
if bx0 >= ax1 or bx1 <= ax0 or by0 >= ay1 or by1 <= ay0:
|
|
103
|
+
return [a]
|
|
104
|
+
out = []
|
|
105
|
+
if by0 > ay0:
|
|
106
|
+
out.append((ax0, ay0, ax1, by0))
|
|
107
|
+
if by1 < ay1:
|
|
108
|
+
out.append((ax0, by1, ax1, ay1))
|
|
109
|
+
my0, my1 = max(ay0, by0), min(ay1, by1)
|
|
110
|
+
if bx0 > ax0:
|
|
111
|
+
out.append((ax0, my0, bx0, my1))
|
|
112
|
+
if bx1 < ax1:
|
|
113
|
+
out.append((bx1, my0, ax1, my1))
|
|
114
|
+
return [r for r in out if r[2] - r[0] > 1e-9 and r[3] - r[1] > 1e-9]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def outline_pieces(rect: Box, width: float, cuts: list[Box]) -> list[Box]:
|
|
118
|
+
"""The area of a rectangle's outline (``width``, centred on the edges, square corners)
|
|
119
|
+
without the ``cuts``, as disjoint rectangles."""
|
|
120
|
+
x0, y0, x1, y1 = rect
|
|
121
|
+
h = width / 2
|
|
122
|
+
if y1 - y0 <= width or x1 - x0 <= width:
|
|
123
|
+
pieces = [(x0 - h, y0 - h, x1 + h, y1 + h)]
|
|
124
|
+
else:
|
|
125
|
+
pieces = [(x0 - h, y0 - h, x1 + h, y0 + h), (x0 - h, y1 - h, x1 + h, y1 + h),
|
|
126
|
+
(x0 - h, y0 + h, x0 + h, y1 - h), (x1 - h, y0 + h, x1 + h, y1 - h)]
|
|
127
|
+
for c in cuts:
|
|
128
|
+
pieces = [q for p in pieces for q in _minus(p, c)]
|
|
129
|
+
return pieces
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def draw_seams(canvas: Canvas, segments: list[tuple[float, ...]], color: Any) -> None:
|
|
133
|
+
"""Draw seams (butt caps, so outlines across their ends stay whole)."""
|
|
134
|
+
if not segments or color is None:
|
|
135
|
+
return
|
|
136
|
+
canvas.draw_segments([s[0] for s in segments], [s[1] for s in segments],
|
|
137
|
+
[s[2] for s in segments], [s[3] for s in segments], stroke=color,
|
|
138
|
+
stroke_width=[s[4] for s in segments], stroke_linecap="butt")
|