plotnine 0.15.0.dev2__py3-none-any.whl → 0.15.0.dev3__py3-none-any.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.
- plotnine/_mpl/layout_manager/_layout_items.py +2 -42
- plotnine/_mpl/layout_manager/_layout_tree.py +168 -115
- plotnine/_mpl/layout_manager/_spaces.py +15 -2
- plotnine/geoms/geom_bar.py +10 -2
- plotnine/geoms/geom_col.py +6 -0
- plotnine/geoms/geom_violin.py +24 -7
- plotnine/mapping/_eval_environment.py +85 -0
- plotnine/mapping/aes.py +6 -23
- plotnine/mapping/evaluation.py +7 -65
- plotnine/stats/stat_sina.py +33 -0
- plotnine/themes/themeable.py +1 -1
- {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.0.dev3.dist-info}/METADATA +1 -1
- {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.0.dev3.dist-info}/RECORD +16 -15
- {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.0.dev3.dist-info}/WHEEL +0 -0
- {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.0.dev3.dist-info}/licenses/LICENSE +0 -0
- {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.0.dev3.dist-info}/top_level.txt +0 -0
|
@@ -299,40 +299,6 @@ class LayoutItems:
|
|
|
299
299
|
|
|
300
300
|
return chain(major, minor)
|
|
301
301
|
|
|
302
|
-
def axis_text_x_margin(self, ax: Axes) -> Iterator[float]:
|
|
303
|
-
"""
|
|
304
|
-
Return XTicks paddings
|
|
305
|
-
"""
|
|
306
|
-
# In plotnine tick padding are specified as a margin to the
|
|
307
|
-
# the axis_text.
|
|
308
|
-
major, minor = [], []
|
|
309
|
-
if not self._is_blank("axis_text_x"):
|
|
310
|
-
h = self.plot.figure.bbox.height
|
|
311
|
-
major = [
|
|
312
|
-
(t.get_pad() or 0) / h for t in ax.xaxis.get_major_ticks()
|
|
313
|
-
]
|
|
314
|
-
minor = [
|
|
315
|
-
(t.get_pad() or 0) / h for t in ax.xaxis.get_minor_ticks()
|
|
316
|
-
]
|
|
317
|
-
return chain(major, minor)
|
|
318
|
-
|
|
319
|
-
def axis_text_y_margin(self, ax: Axes) -> Iterator[float]:
|
|
320
|
-
"""
|
|
321
|
-
Return YTicks paddings
|
|
322
|
-
"""
|
|
323
|
-
# In plotnine tick padding are specified as a margin to the
|
|
324
|
-
# the axis_text.
|
|
325
|
-
major, minor = [], []
|
|
326
|
-
if not self._is_blank("axis_text_y"):
|
|
327
|
-
w = self.plot.figure.bbox.width
|
|
328
|
-
major = [
|
|
329
|
-
(t.get_pad() or 0) / w for t in ax.yaxis.get_major_ticks()
|
|
330
|
-
]
|
|
331
|
-
minor = [
|
|
332
|
-
(t.get_pad() or 0) / w for t in ax.yaxis.get_minor_ticks()
|
|
333
|
-
]
|
|
334
|
-
return chain(major, minor)
|
|
335
|
-
|
|
336
302
|
def strip_text_x_height(self, position: StripPosition) -> float:
|
|
337
303
|
"""
|
|
338
304
|
Height taken up by the top strips
|
|
@@ -377,10 +343,7 @@ class LayoutItems:
|
|
|
377
343
|
Return maximum height[figure space] of x tick labels
|
|
378
344
|
"""
|
|
379
345
|
heights = [
|
|
380
|
-
self.calc.tight_height(label)
|
|
381
|
-
for label, pad in zip(
|
|
382
|
-
self.axis_text_x(ax), self.axis_text_x_margin(ax)
|
|
383
|
-
)
|
|
346
|
+
self.calc.tight_height(label) for label in self.axis_text_x(ax)
|
|
384
347
|
]
|
|
385
348
|
return max(heights) if len(heights) else 0
|
|
386
349
|
|
|
@@ -410,10 +373,7 @@ class LayoutItems:
|
|
|
410
373
|
Return maximum width[figure space] of y tick labels
|
|
411
374
|
"""
|
|
412
375
|
widths = [
|
|
413
|
-
self.calc.tight_width(label)
|
|
414
|
-
for label, pad in zip(
|
|
415
|
-
self.axis_text_y(ax), self.axis_text_y_margin(ax)
|
|
416
|
-
)
|
|
376
|
+
self.calc.tight_width(label) for label in self.axis_text_y(ax)
|
|
417
377
|
]
|
|
418
378
|
return max(widths) if len(widths) else 0
|
|
419
379
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
|
-
from contextlib import suppress
|
|
5
4
|
from dataclasses import dataclass
|
|
6
5
|
from functools import cached_property
|
|
7
6
|
from typing import TYPE_CHECKING
|
|
@@ -81,6 +80,13 @@ class LayoutTree:
|
|
|
81
80
|
represents.
|
|
82
81
|
"""
|
|
83
82
|
|
|
83
|
+
@cached_property
|
|
84
|
+
def sub_compositions(self) -> list[LayoutTree]:
|
|
85
|
+
"""
|
|
86
|
+
LayoutTrees of the direct sub compositions of this one
|
|
87
|
+
"""
|
|
88
|
+
return [item for item in self.nodes if isinstance(item, LayoutTree)]
|
|
89
|
+
|
|
84
90
|
@cached_property
|
|
85
91
|
@abc.abstractmethod
|
|
86
92
|
def lefts(self) -> Sequence[float]:
|
|
@@ -141,89 +147,54 @@ class LayoutTree:
|
|
|
141
147
|
In figure dimenstions
|
|
142
148
|
"""
|
|
143
149
|
|
|
144
|
-
|
|
150
|
+
@abc.abstractmethod
|
|
151
|
+
def align(self):
|
|
145
152
|
"""
|
|
146
|
-
Align the
|
|
153
|
+
Align all the edges in this composition & contained compositions
|
|
147
154
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|# | | # |
|
|
151
|
-
|# | | # |
|
|
152
|
-
|-----------| -> |-----------|
|
|
153
|
-
| # | | # |
|
|
154
|
-
| # | | # |
|
|
155
|
-
| # | | # |
|
|
156
|
-
----------- -----------
|
|
155
|
+
This function mutates the layout spaces, specifically the
|
|
156
|
+
alignment_margins along the sides of the plot.
|
|
157
157
|
"""
|
|
158
158
|
|
|
159
|
-
def
|
|
159
|
+
def align_sub_compositions(self):
|
|
160
160
|
"""
|
|
161
|
-
Align the
|
|
162
|
-
|
|
163
|
-
----------- -----------
|
|
164
|
-
| | | | | |
|
|
165
|
-
| | | | | |
|
|
166
|
-
| | | -> | | |
|
|
167
|
-
| |#####| |#####|#####|
|
|
168
|
-
|#####| | | | |
|
|
169
|
-
----------- -----------
|
|
161
|
+
Align the compositions contained in this one
|
|
170
162
|
"""
|
|
163
|
+
# Recurse into the contained compositions
|
|
164
|
+
for tree in self.sub_compositions:
|
|
165
|
+
tree.align()
|
|
171
166
|
|
|
172
|
-
|
|
167
|
+
@property
|
|
168
|
+
def bottoms_align(self) -> bool:
|
|
173
169
|
"""
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
----------- -----------
|
|
177
|
-
| # | | # |
|
|
178
|
-
| # | | # |
|
|
179
|
-
| # | | # |
|
|
180
|
-
|-----------| -> |-----------|
|
|
181
|
-
| #| | # |
|
|
182
|
-
| #| | # |
|
|
183
|
-
| #| | # |
|
|
184
|
-
----------- -----------
|
|
170
|
+
Return True if panel bottoms for the nodes are aligned
|
|
185
171
|
"""
|
|
172
|
+
arr = np.array(self.bottoms)
|
|
173
|
+
return all(arr == arr[0])
|
|
186
174
|
|
|
187
|
-
|
|
175
|
+
@property
|
|
176
|
+
def lefts_align(self) -> bool:
|
|
188
177
|
"""
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
----------- -----------
|
|
192
|
-
|#####| | | | |
|
|
193
|
-
| |#####| |#####|#####|
|
|
194
|
-
| | | -> | | |
|
|
195
|
-
| | | | | |
|
|
196
|
-
| | | | | |
|
|
197
|
-
----------- -----------
|
|
178
|
+
Return True if panel lefts for the nodes are aligned
|
|
198
179
|
"""
|
|
180
|
+
arr = np.array(self.lefts)
|
|
181
|
+
return all(arr == arr[0])
|
|
199
182
|
|
|
200
|
-
|
|
183
|
+
@property
|
|
184
|
+
def tops_align(self) -> bool:
|
|
201
185
|
"""
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
This function mutates the layout spaces, specifically the
|
|
205
|
-
alignment_margins along the sides of the plot.
|
|
186
|
+
Return True if panel tops for the nodes are aligned
|
|
206
187
|
"""
|
|
207
|
-
self.
|
|
208
|
-
|
|
209
|
-
self.align_rights()
|
|
210
|
-
self.align_tops()
|
|
211
|
-
|
|
212
|
-
for item in self.nodes:
|
|
213
|
-
if isinstance(item, LayoutTree):
|
|
214
|
-
item.align()
|
|
215
|
-
|
|
216
|
-
with suppress(AttributeError):
|
|
217
|
-
del self.lefts
|
|
218
|
-
|
|
219
|
-
with suppress(AttributeError):
|
|
220
|
-
del self.bottoms
|
|
188
|
+
arr = np.array(self.tops)
|
|
189
|
+
return all(arr == arr[0])
|
|
221
190
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
191
|
+
@property
|
|
192
|
+
def rights_align(self) -> bool:
|
|
193
|
+
"""
|
|
194
|
+
Return True if panel rights for the nodes are aligned
|
|
195
|
+
"""
|
|
196
|
+
arr = np.array(self.rights)
|
|
197
|
+
return all(arr == arr[0])
|
|
227
198
|
|
|
228
199
|
@property
|
|
229
200
|
@abc.abstractmethod
|
|
@@ -288,22 +259,13 @@ class LayoutTree:
|
|
|
288
259
|
This function mutates the composition gridspecs; specifically the
|
|
289
260
|
width_ratios and height_ratios.
|
|
290
261
|
"""
|
|
291
|
-
self.resize_widths()
|
|
292
|
-
self.resize_heights()
|
|
293
262
|
|
|
294
|
-
|
|
295
|
-
if isinstance(item, LayoutTree):
|
|
296
|
-
item.resize()
|
|
297
|
-
|
|
298
|
-
def resize_widths(self):
|
|
263
|
+
def resize_sub_compositions(self):
|
|
299
264
|
"""
|
|
300
|
-
Resize
|
|
301
|
-
"""
|
|
302
|
-
|
|
303
|
-
def resize_heights(self):
|
|
304
|
-
"""
|
|
305
|
-
Resize the heights of gridspec so that panels have equal heights
|
|
265
|
+
Resize panels in the compositions contained in this one
|
|
306
266
|
"""
|
|
267
|
+
for tree in self.sub_compositions:
|
|
268
|
+
tree.resize()
|
|
307
269
|
|
|
308
270
|
@staticmethod
|
|
309
271
|
def create(
|
|
@@ -368,6 +330,11 @@ class ColumnsTree(LayoutTree):
|
|
|
368
330
|
-------------------
|
|
369
331
|
"""
|
|
370
332
|
|
|
333
|
+
def align(self):
|
|
334
|
+
self.align_tops()
|
|
335
|
+
self.align_bottoms()
|
|
336
|
+
self.align_sub_compositions()
|
|
337
|
+
|
|
371
338
|
@cached_property
|
|
372
339
|
def lefts(self):
|
|
373
340
|
left_item = self.nodes[0]
|
|
@@ -383,14 +350,6 @@ class ColumnsTree(LayoutTree):
|
|
|
383
350
|
else:
|
|
384
351
|
left_item.set_left_alignment_margin(value)
|
|
385
352
|
|
|
386
|
-
def align_bottoms(self):
|
|
387
|
-
values = max(self.bottoms) - np.array(self.bottoms)
|
|
388
|
-
for item, value in zip(self.nodes, values):
|
|
389
|
-
if isinstance(item, LayoutSpaces):
|
|
390
|
-
item.b.alignment_margin = value
|
|
391
|
-
else:
|
|
392
|
-
item.set_bottom_alignment_margin(value)
|
|
393
|
-
|
|
394
353
|
@cached_property
|
|
395
354
|
def bottoms(self):
|
|
396
355
|
values = []
|
|
@@ -401,6 +360,33 @@ class ColumnsTree(LayoutTree):
|
|
|
401
360
|
values.append(max(item.bottoms))
|
|
402
361
|
return values
|
|
403
362
|
|
|
363
|
+
def align_bottoms(self):
|
|
364
|
+
"""
|
|
365
|
+
Align the immediate bottom edges this composition
|
|
366
|
+
|
|
367
|
+
----------- -----------
|
|
368
|
+
| | | | | |
|
|
369
|
+
| | | | | |
|
|
370
|
+
| | | -> | | |
|
|
371
|
+
| |#####| |#####|#####|
|
|
372
|
+
|#####| | | | |
|
|
373
|
+
----------- -----------
|
|
374
|
+
"""
|
|
375
|
+
# If panels are aligned and have a non-zero alignment_margin,
|
|
376
|
+
# aligning them again will set that value to zero and undoes
|
|
377
|
+
# the alignment.
|
|
378
|
+
if self.bottoms_align:
|
|
379
|
+
return
|
|
380
|
+
|
|
381
|
+
values = max(self.bottoms) - np.array(self.bottoms)
|
|
382
|
+
for item, value in zip(self.nodes, values):
|
|
383
|
+
if isinstance(item, LayoutSpaces):
|
|
384
|
+
item.b.alignment_margin = value
|
|
385
|
+
else:
|
|
386
|
+
item.set_bottom_alignment_margin(value)
|
|
387
|
+
|
|
388
|
+
del self.bottoms
|
|
389
|
+
|
|
404
390
|
def set_bottom_alignment_margin(self, value: float):
|
|
405
391
|
for item in self.nodes:
|
|
406
392
|
if isinstance(item, LayoutSpaces):
|
|
@@ -423,14 +409,6 @@ class ColumnsTree(LayoutTree):
|
|
|
423
409
|
else:
|
|
424
410
|
right_item.set_right_alignment_margin(value)
|
|
425
411
|
|
|
426
|
-
def align_tops(self):
|
|
427
|
-
values = np.array(self.tops) - min(self.tops)
|
|
428
|
-
for item, value in zip(self.nodes, values):
|
|
429
|
-
if isinstance(item, LayoutSpaces):
|
|
430
|
-
item.t.alignment_margin = value
|
|
431
|
-
else:
|
|
432
|
-
item.set_top_alignment_margin(value)
|
|
433
|
-
|
|
434
412
|
@cached_property
|
|
435
413
|
def tops(self):
|
|
436
414
|
values = []
|
|
@@ -441,6 +419,30 @@ class ColumnsTree(LayoutTree):
|
|
|
441
419
|
values.append(min(item.tops))
|
|
442
420
|
return values
|
|
443
421
|
|
|
422
|
+
def align_tops(self):
|
|
423
|
+
"""
|
|
424
|
+
Align the immediate top edges in this composition
|
|
425
|
+
|
|
426
|
+
----------- -----------
|
|
427
|
+
|#####| | | | |
|
|
428
|
+
| |#####| |#####|#####|
|
|
429
|
+
| | | -> | | |
|
|
430
|
+
| | | | | |
|
|
431
|
+
| | | | | |
|
|
432
|
+
----------- -----------
|
|
433
|
+
"""
|
|
434
|
+
if self.tops_align:
|
|
435
|
+
return
|
|
436
|
+
|
|
437
|
+
values = np.array(self.tops) - min(self.tops)
|
|
438
|
+
for item, value in zip(self.nodes, values):
|
|
439
|
+
if isinstance(item, LayoutSpaces):
|
|
440
|
+
item.t.alignment_margin = value
|
|
441
|
+
else:
|
|
442
|
+
item.set_top_alignment_margin(value)
|
|
443
|
+
|
|
444
|
+
del self.tops
|
|
445
|
+
|
|
444
446
|
def set_top_alignment_margin(self, value: float):
|
|
445
447
|
for item in self.nodes:
|
|
446
448
|
if isinstance(item, LayoutSpaces):
|
|
@@ -476,7 +478,10 @@ class ColumnsTree(LayoutTree):
|
|
|
476
478
|
"""
|
|
477
479
|
return max(self.plot_heights)
|
|
478
480
|
|
|
479
|
-
def
|
|
481
|
+
def resize(self):
|
|
482
|
+
"""
|
|
483
|
+
Resize the widths of gridspec so that panels have equal widths
|
|
484
|
+
"""
|
|
480
485
|
# The new width of each panel is the average width of all
|
|
481
486
|
# the panels plus all the space to the left and right
|
|
482
487
|
# of the panels.
|
|
@@ -486,6 +491,7 @@ class ColumnsTree(LayoutTree):
|
|
|
486
491
|
new_plot_widths = panel_widths.mean() + non_panel_space
|
|
487
492
|
width_ratios = new_plot_widths / new_plot_widths.min()
|
|
488
493
|
self.gridspec.set_width_ratios(width_ratios)
|
|
494
|
+
self.resize_sub_compositions()
|
|
489
495
|
|
|
490
496
|
|
|
491
497
|
@dataclass
|
|
@@ -506,13 +512,10 @@ class RowsTree(LayoutTree):
|
|
|
506
512
|
-------------------
|
|
507
513
|
"""
|
|
508
514
|
|
|
509
|
-
def
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
item.l.alignment_margin = value
|
|
514
|
-
else:
|
|
515
|
-
item.set_left_alignment_margin(value)
|
|
515
|
+
def align(self):
|
|
516
|
+
self.align_lefts()
|
|
517
|
+
self.align_rights()
|
|
518
|
+
self.align_sub_compositions()
|
|
516
519
|
|
|
517
520
|
@cached_property
|
|
518
521
|
def lefts(self):
|
|
@@ -524,6 +527,32 @@ class RowsTree(LayoutTree):
|
|
|
524
527
|
values.append(max(item.lefts))
|
|
525
528
|
return values
|
|
526
529
|
|
|
530
|
+
def align_lefts(self):
|
|
531
|
+
"""
|
|
532
|
+
Align the immediate left edges in this composition
|
|
533
|
+
|
|
534
|
+
----------- -----------
|
|
535
|
+
|# | | # |
|
|
536
|
+
|# | | # |
|
|
537
|
+
|# | | # |
|
|
538
|
+
|-----------| -> |-----------|
|
|
539
|
+
| # | | # |
|
|
540
|
+
| # | | # |
|
|
541
|
+
| # | | # |
|
|
542
|
+
----------- -----------
|
|
543
|
+
"""
|
|
544
|
+
if self.lefts_align:
|
|
545
|
+
return
|
|
546
|
+
|
|
547
|
+
values = max(self.lefts) - np.array(self.lefts)
|
|
548
|
+
for item, value in zip(self.nodes, values):
|
|
549
|
+
if isinstance(item, LayoutSpaces):
|
|
550
|
+
item.l.alignment_margin = value
|
|
551
|
+
else:
|
|
552
|
+
item.set_left_alignment_margin(value)
|
|
553
|
+
|
|
554
|
+
del self.lefts
|
|
555
|
+
|
|
527
556
|
def set_left_alignment_margin(self, value: float):
|
|
528
557
|
for item in self.nodes:
|
|
529
558
|
if isinstance(item, LayoutSpaces):
|
|
@@ -546,14 +575,6 @@ class RowsTree(LayoutTree):
|
|
|
546
575
|
else:
|
|
547
576
|
bottom_item.set_bottom_alignment_margin(value)
|
|
548
577
|
|
|
549
|
-
def align_rights(self):
|
|
550
|
-
values = np.array(self.rights) - min(self.rights)
|
|
551
|
-
for item, value in zip(self.nodes, values):
|
|
552
|
-
if isinstance(item, LayoutSpaces):
|
|
553
|
-
item.r.alignment_margin = value
|
|
554
|
-
else:
|
|
555
|
-
item.set_right_alignment_margin(value)
|
|
556
|
-
|
|
557
578
|
@cached_property
|
|
558
579
|
def rights(self):
|
|
559
580
|
values = []
|
|
@@ -564,6 +585,32 @@ class RowsTree(LayoutTree):
|
|
|
564
585
|
values.append(min(item.rights))
|
|
565
586
|
return values
|
|
566
587
|
|
|
588
|
+
def align_rights(self):
|
|
589
|
+
"""
|
|
590
|
+
Align the immediate right edges in this composition
|
|
591
|
+
|
|
592
|
+
----------- -----------
|
|
593
|
+
| # | | # |
|
|
594
|
+
| # | | # |
|
|
595
|
+
| # | | # |
|
|
596
|
+
|-----------| -> |-----------|
|
|
597
|
+
| #| | # |
|
|
598
|
+
| #| | # |
|
|
599
|
+
| #| | # |
|
|
600
|
+
----------- -----------
|
|
601
|
+
"""
|
|
602
|
+
if self.rights_align:
|
|
603
|
+
return
|
|
604
|
+
|
|
605
|
+
values = np.array(self.rights) - min(self.rights)
|
|
606
|
+
for item, value in zip(self.nodes, values):
|
|
607
|
+
if isinstance(item, LayoutSpaces):
|
|
608
|
+
item.r.alignment_margin = value
|
|
609
|
+
else:
|
|
610
|
+
item.set_right_alignment_margin(value)
|
|
611
|
+
|
|
612
|
+
del self.rights
|
|
613
|
+
|
|
567
614
|
def set_right_alignment_margin(self, value: float):
|
|
568
615
|
for item in self.nodes:
|
|
569
616
|
if isinstance(item, LayoutSpaces):
|
|
@@ -614,8 +661,13 @@ class RowsTree(LayoutTree):
|
|
|
614
661
|
"""
|
|
615
662
|
return sum(self.plot_heights)
|
|
616
663
|
|
|
617
|
-
def
|
|
618
|
-
|
|
664
|
+
def resize(self):
|
|
665
|
+
"""
|
|
666
|
+
Resize the heights of gridspec so that panels have equal heights
|
|
667
|
+
|
|
668
|
+
This method resizes (recursively) the contained compositions
|
|
669
|
+
"""
|
|
670
|
+
# The new height of each panel is the average width of all
|
|
619
671
|
# the panels plus all the space above and below the panels.
|
|
620
672
|
plot_heights = np.array(self.plot_heights)
|
|
621
673
|
panel_heights = np.array(self.panel_heights)
|
|
@@ -623,3 +675,4 @@ class RowsTree(LayoutTree):
|
|
|
623
675
|
new_plot_heights = panel_heights.mean() + non_panel_space
|
|
624
676
|
height_ratios = new_plot_heights / new_plot_heights.max()
|
|
625
677
|
self.gridspec.set_height_ratios(height_ratios)
|
|
678
|
+
self.resize_sub_compositions()
|
|
@@ -222,7 +222,9 @@ class left_spaces(_side_spaces):
|
|
|
222
222
|
axis_title_y_margin_left: float = 0
|
|
223
223
|
axis_title_y: float = 0
|
|
224
224
|
axis_title_y_margin_right: float = 0
|
|
225
|
+
axis_text_y_margin_left: float = 0
|
|
225
226
|
axis_text_y: float = 0
|
|
227
|
+
axis_text_y_margin_right: float = 0
|
|
226
228
|
axis_ticks_y: float = 0
|
|
227
229
|
|
|
228
230
|
def _calculate(self):
|
|
@@ -256,6 +258,11 @@ class left_spaces(_side_spaces):
|
|
|
256
258
|
|
|
257
259
|
# Account for the space consumed by the axis
|
|
258
260
|
self.axis_text_y = items.axis_text_y_max_width_at("first_col")
|
|
261
|
+
if self.axis_text_y:
|
|
262
|
+
m = theme.get_margin("axis_text_y").fig
|
|
263
|
+
self.axis_text_y_margin_left = m.l
|
|
264
|
+
self.axis_text_y_margin_right = m.r
|
|
265
|
+
|
|
259
266
|
self.axis_ticks_y = items.axis_ticks_y_max_width_at("first_col")
|
|
260
267
|
|
|
261
268
|
# Adjust plot_margin to make room for ylabels that protude well
|
|
@@ -576,7 +583,9 @@ class bottom_spaces(_side_spaces):
|
|
|
576
583
|
axis_title_x_margin_bottom: float = 0
|
|
577
584
|
axis_title_x: float = 0
|
|
578
585
|
axis_title_x_margin_top: float = 0
|
|
586
|
+
axis_text_x_margin_bottom: float = 0
|
|
579
587
|
axis_text_x: float = 0
|
|
588
|
+
axis_text_x_margin_top: float = 0
|
|
580
589
|
axis_ticks_x: float = 0
|
|
581
590
|
|
|
582
591
|
def _calculate(self):
|
|
@@ -616,8 +625,12 @@ class bottom_spaces(_side_spaces):
|
|
|
616
625
|
self.axis_title_x_margin_top = m.t * F
|
|
617
626
|
|
|
618
627
|
# Account for the space consumed by the axis
|
|
619
|
-
self.axis_ticks_x = items.axis_ticks_x_max_height_at("last_row")
|
|
620
628
|
self.axis_text_x = items.axis_text_x_max_height_at("last_row")
|
|
629
|
+
if self.axis_text_x:
|
|
630
|
+
m = theme.get_margin("axis_text_x").fig
|
|
631
|
+
self.axis_text_x_margin_bottom = m.b
|
|
632
|
+
self.axis_text_x_margin_top = m.t
|
|
633
|
+
self.axis_ticks_x = items.axis_ticks_x_max_height_at("last_row")
|
|
621
634
|
|
|
622
635
|
# Adjust plot_margin to make room for ylabels that protude well
|
|
623
636
|
# beyond the axes
|
|
@@ -736,7 +749,7 @@ class LayoutSpaces:
|
|
|
736
749
|
sw: float = field(init=False, default=0)
|
|
737
750
|
"""vertical spacing btn panels w.r.t figure"""
|
|
738
751
|
|
|
739
|
-
gsparams: GridSpecParams = field(init=False)
|
|
752
|
+
gsparams: GridSpecParams = field(init=False, repr=False)
|
|
740
753
|
"""Grid spacing btn panels w.r.t figure"""
|
|
741
754
|
|
|
742
755
|
def __post_init__(self):
|
plotnine/geoms/geom_bar.py
CHANGED
|
@@ -20,6 +20,11 @@ class geom_bar(geom_rect):
|
|
|
20
20
|
Parameters
|
|
21
21
|
----------
|
|
22
22
|
{common_parameters}
|
|
23
|
+
just : float, default=0.5
|
|
24
|
+
How to align the column with respect to the axis breaks. The default
|
|
25
|
+
`0.5` aligns the center of the column with the break. `0` aligns the
|
|
26
|
+
left of the of the column with the break and `1` aligns the right of
|
|
27
|
+
the column with the break.
|
|
23
28
|
width : float, default=None
|
|
24
29
|
Bar width. If `None`{.py}, the width is set to
|
|
25
30
|
`90%` of the resolution of the data.
|
|
@@ -35,6 +40,7 @@ class geom_bar(geom_rect):
|
|
|
35
40
|
"stat": "count",
|
|
36
41
|
"position": "stack",
|
|
37
42
|
"na_rm": False,
|
|
43
|
+
"just": 0.5,
|
|
38
44
|
"width": None,
|
|
39
45
|
}
|
|
40
46
|
|
|
@@ -45,6 +51,8 @@ class geom_bar(geom_rect):
|
|
|
45
51
|
else:
|
|
46
52
|
data["width"] = resolution(data["x"], False) * 0.9
|
|
47
53
|
|
|
54
|
+
just = self.params.get("just", 0.5)
|
|
55
|
+
|
|
48
56
|
bool_idx = data["y"] < 0
|
|
49
57
|
|
|
50
58
|
data["ymin"] = 0.0
|
|
@@ -53,7 +61,7 @@ class geom_bar(geom_rect):
|
|
|
53
61
|
data["ymax"] = data["y"]
|
|
54
62
|
data.loc[bool_idx, "ymax"] = 0.0
|
|
55
63
|
|
|
56
|
-
data["xmin"] = data["x"] - data["width"]
|
|
57
|
-
data["xmax"] = data["x"] + data["width"]
|
|
64
|
+
data["xmin"] = data["x"] - data["width"] * just
|
|
65
|
+
data["xmax"] = data["x"] + data["width"] * (1 - just)
|
|
58
66
|
del data["width"]
|
|
59
67
|
return data
|
plotnine/geoms/geom_col.py
CHANGED
|
@@ -17,6 +17,11 @@ class geom_col(geom_bar):
|
|
|
17
17
|
Parameters
|
|
18
18
|
----------
|
|
19
19
|
{common_parameters}
|
|
20
|
+
just : float, default=0.5
|
|
21
|
+
How to align the column with respect to the axis breaks. The default
|
|
22
|
+
`0.5` aligns the center of the column with the break. `0` aligns the
|
|
23
|
+
left of the of the column with the break and `1` aligns the right of
|
|
24
|
+
the column with the break.
|
|
20
25
|
width : float, default=None
|
|
21
26
|
Bar width. If `None`{.py}, the width is set to
|
|
22
27
|
`90%` of the resolution of the data.
|
|
@@ -32,5 +37,6 @@ class geom_col(geom_bar):
|
|
|
32
37
|
"stat": "identity",
|
|
33
38
|
"position": "stack",
|
|
34
39
|
"na_rm": False,
|
|
40
|
+
"just": 0.5,
|
|
35
41
|
"width": None,
|
|
36
42
|
}
|
plotnine/geoms/geom_violin.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from typing import TYPE_CHECKING, cast
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import pandas as pd
|
|
@@ -11,7 +11,7 @@ from .geom import geom
|
|
|
11
11
|
from .geom_path import geom_path
|
|
12
12
|
from .geom_polygon import geom_polygon
|
|
13
13
|
|
|
14
|
-
if
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
15
|
from typing import Any
|
|
16
16
|
|
|
17
17
|
from matplotlib.axes import Axes
|
|
@@ -115,10 +115,17 @@ class geom_violin(geom):
|
|
|
115
115
|
ax: Axes,
|
|
116
116
|
**params: Any,
|
|
117
117
|
):
|
|
118
|
-
quantiles = params
|
|
119
|
-
style = params
|
|
118
|
+
quantiles = params.pop("draw_quantiles")
|
|
119
|
+
style = params.pop("style")
|
|
120
|
+
zorder = params.pop("zorder")
|
|
121
|
+
|
|
122
|
+
for i, (group, df) in enumerate(data.groupby("group")):
|
|
123
|
+
# Place the violins with the smalleer group number on top
|
|
124
|
+
# of those with larger numbers. The group_zorder values should be
|
|
125
|
+
# in the range [zorder, zorder + 1) to stay within the layer.
|
|
126
|
+
group = cast("int", group)
|
|
127
|
+
group_zorder = zorder + 0.9 / group
|
|
120
128
|
|
|
121
|
-
for i, (_, df) in enumerate(data.groupby("group")):
|
|
122
129
|
# Find the points for the line to go all the way around
|
|
123
130
|
df["xminv"] = df["x"] - df["violinwidth"] * (df["x"] - df["xmin"])
|
|
124
131
|
df["xmaxv"] = df["x"] + df["violinwidth"] * (df["xmax"] - df["x"])
|
|
@@ -156,7 +163,12 @@ class geom_violin(geom):
|
|
|
156
163
|
|
|
157
164
|
# plot violin polygon
|
|
158
165
|
geom_polygon.draw_group(
|
|
159
|
-
polygon_df,
|
|
166
|
+
polygon_df,
|
|
167
|
+
panel_params,
|
|
168
|
+
coord,
|
|
169
|
+
ax,
|
|
170
|
+
zorder=group_zorder,
|
|
171
|
+
**params,
|
|
160
172
|
)
|
|
161
173
|
|
|
162
174
|
if quantiles is not None:
|
|
@@ -174,7 +186,12 @@ class geom_violin(geom):
|
|
|
174
186
|
|
|
175
187
|
# plot quantile segments
|
|
176
188
|
geom_path.draw_group(
|
|
177
|
-
segment_df,
|
|
189
|
+
segment_df,
|
|
190
|
+
panel_params,
|
|
191
|
+
coord,
|
|
192
|
+
ax,
|
|
193
|
+
zorder=group_zorder,
|
|
194
|
+
**params,
|
|
178
195
|
)
|
|
179
196
|
|
|
180
197
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
These are functions that can be called by the user inside the aes()
|
|
3
|
+
mapping. This is meant to make it easy to transform column-variables
|
|
4
|
+
as easily as is possible in ggplot2.
|
|
5
|
+
|
|
6
|
+
We only implement the most common functions.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import pandas as pd
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from typing import Any, Sequence
|
|
18
|
+
|
|
19
|
+
__all__ = (
|
|
20
|
+
"factor",
|
|
21
|
+
"reorder",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def factor(
|
|
26
|
+
values: Sequence[Any],
|
|
27
|
+
categories: Sequence[Any] | None = None,
|
|
28
|
+
ordered: bool | None = None,
|
|
29
|
+
) -> pd.Categorical:
|
|
30
|
+
"""
|
|
31
|
+
Turn x in to a categorical (factor) variable
|
|
32
|
+
|
|
33
|
+
It is just an alias to `pandas.Categorical`
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
values :
|
|
38
|
+
The values of the categorical. If categories are given, values not in
|
|
39
|
+
categories will be replaced with NaN.
|
|
40
|
+
categories :
|
|
41
|
+
The unique categories for this categorical. If not given, the
|
|
42
|
+
categories are assumed to be the unique values of `values`
|
|
43
|
+
(sorted, if possible, otherwise in the order in which they appear).
|
|
44
|
+
ordered :
|
|
45
|
+
Whether or not this categorical is treated as a ordered categorical.
|
|
46
|
+
If True, the resulting categorical will be ordered.
|
|
47
|
+
An ordered categorical respects, when sorted, the order of its
|
|
48
|
+
`categories` attribute (which in turn is the `categories` argument, if
|
|
49
|
+
provided).
|
|
50
|
+
"""
|
|
51
|
+
return pd.Categorical(values, categories=categories, ordered=None)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def reorder(x, y, fun=np.median, ascending=True):
|
|
55
|
+
"""
|
|
56
|
+
Reorder categorical by sorting along another variable
|
|
57
|
+
|
|
58
|
+
It is the order of the categories that changes. Values in x
|
|
59
|
+
are grouped by categories and summarised to determine the
|
|
60
|
+
new order.
|
|
61
|
+
|
|
62
|
+
Credit: Copied from plydata
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
x : list-like
|
|
67
|
+
Values that will make up the categorical.
|
|
68
|
+
y : list-like
|
|
69
|
+
Values by which `c` will be ordered.
|
|
70
|
+
fun : callable
|
|
71
|
+
Summarising function to `x` for each category in `c`.
|
|
72
|
+
Default is the *median*.
|
|
73
|
+
ascending : bool
|
|
74
|
+
If `True`, the `c` is ordered in ascending order of `x`.
|
|
75
|
+
"""
|
|
76
|
+
if len(x) != len(y):
|
|
77
|
+
raise ValueError(f"Lengths are not equal. {len(x)=}, {len(x)=}")
|
|
78
|
+
summary = (
|
|
79
|
+
pd.Series(y)
|
|
80
|
+
.groupby(x, observed=True)
|
|
81
|
+
.apply(fun)
|
|
82
|
+
.sort_values(ascending=ascending)
|
|
83
|
+
)
|
|
84
|
+
cats = summary.index.to_list()
|
|
85
|
+
return pd.Categorical(x, categories=cats)
|
plotnine/mapping/aes.py
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
import typing
|
|
5
4
|
from collections.abc import Iterable, Sequence
|
|
6
5
|
from contextlib import suppress
|
|
7
6
|
from copy import deepcopy
|
|
8
7
|
from dataclasses import fields
|
|
9
8
|
from functools import cached_property
|
|
10
|
-
from typing import Any, Dict
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Dict
|
|
11
10
|
|
|
12
11
|
import pandas as pd
|
|
13
12
|
|
|
14
13
|
from ..iapi import labels_view
|
|
15
14
|
from .evaluation import after_stat, stage
|
|
16
15
|
|
|
17
|
-
if
|
|
16
|
+
if TYPE_CHECKING:
|
|
18
17
|
from typing import Protocol, TypeVar
|
|
19
18
|
|
|
20
19
|
class ColorOrColour(Protocol):
|
|
@@ -172,27 +171,11 @@ class aes(Dict[str, Any]):
|
|
|
172
171
|
ggplot(df, aes(x="df.index", y="np.sin(gam ma)"))
|
|
173
172
|
```
|
|
174
173
|
|
|
175
|
-
`aes` has 2 internal
|
|
176
|
-
|
|
174
|
+
`aes` has 2 internal functions that you can use in your expressions
|
|
175
|
+
when transforming the variables.
|
|
177
176
|
|
|
178
|
-
1.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
```python
|
|
182
|
-
ggplot(mtcars, aes(x="factor(cyl)")) + geom_bar()
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
2. `reorder` - This function changes the order of first variable
|
|
186
|
-
based on values of the second variable:
|
|
187
|
-
|
|
188
|
-
```python
|
|
189
|
-
df = pd.DataFrame({
|
|
190
|
-
"x": ["b", "d", "c", "a"],
|
|
191
|
-
"y": [1, 2, 3, 4]
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
ggplot(df, aes("reorder(x, y)", "y")) + geom_col()
|
|
195
|
-
```
|
|
177
|
+
1. [](:func:`~plotnine.mapping._eval_environment.factor`)
|
|
178
|
+
1. [](:func:`~plotnine.mapping._eval_environment.reorder`)
|
|
196
179
|
|
|
197
180
|
**The group aesthetic**
|
|
198
181
|
|
plotnine/mapping/evaluation.py
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import numbers
|
|
4
|
-
import
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import pandas as pd
|
|
8
8
|
import pandas.api.types as pdtypes
|
|
9
9
|
|
|
10
10
|
from ..exceptions import PlotnineError
|
|
11
|
+
from ._eval_environment import factor, reorder
|
|
11
12
|
|
|
12
|
-
if
|
|
13
|
+
if TYPE_CHECKING:
|
|
13
14
|
from typing import Any
|
|
14
15
|
|
|
15
16
|
from . import aes
|
|
@@ -18,6 +19,9 @@ if typing.TYPE_CHECKING:
|
|
|
18
19
|
|
|
19
20
|
__all__ = ("after_stat", "after_scale", "stage")
|
|
20
21
|
|
|
22
|
+
|
|
23
|
+
EVAL_ENVIRONMENT = {"factor": factor, "reorder": reorder}
|
|
24
|
+
|
|
21
25
|
_TPL_EVAL_FAIL = """\
|
|
22
26
|
Could not evaluate the '{}' mapping: '{}' \
|
|
23
27
|
(original error: {})"""
|
|
@@ -108,68 +112,6 @@ def after_scale(x):
|
|
|
108
112
|
return stage(after_scale=x)
|
|
109
113
|
|
|
110
114
|
|
|
111
|
-
def reorder(x, y, fun=np.median, ascending=True):
|
|
112
|
-
"""
|
|
113
|
-
Reorder categorical by sorting along another variable
|
|
114
|
-
|
|
115
|
-
It is the order of the categories that changes. Values in x
|
|
116
|
-
are grouped by categories and summarised to determine the
|
|
117
|
-
new order.
|
|
118
|
-
|
|
119
|
-
Credit: Copied from plydata
|
|
120
|
-
|
|
121
|
-
Parameters
|
|
122
|
-
----------
|
|
123
|
-
x : list-like
|
|
124
|
-
Values that will make up the categorical.
|
|
125
|
-
y : list-like
|
|
126
|
-
Values by which `c` will be ordered.
|
|
127
|
-
fun : callable
|
|
128
|
-
Summarising function to `x` for each category in `c`.
|
|
129
|
-
Default is the *median*.
|
|
130
|
-
ascending : bool
|
|
131
|
-
If `True`, the `c` is ordered in ascending order of `x`.
|
|
132
|
-
|
|
133
|
-
Examples
|
|
134
|
-
--------
|
|
135
|
-
>>> c = list('abbccc')
|
|
136
|
-
>>> x = [11, 2, 2, 3, 33, 3]
|
|
137
|
-
>>> cat_reorder(c, x)
|
|
138
|
-
[a, b, b, c, c, c]
|
|
139
|
-
Categories (3, object): [b, c, a]
|
|
140
|
-
>>> cat_reorder(c, x, fun=max)
|
|
141
|
-
[a, b, b, c, c, c]
|
|
142
|
-
Categories (3, object): [b, a, c]
|
|
143
|
-
>>> cat_reorder(c, x, fun=max, ascending=False)
|
|
144
|
-
[a, b, b, c, c, c]
|
|
145
|
-
Categories (3, object): [c, a, b]
|
|
146
|
-
>>> c_ordered = pd.Categorical(c, ordered=True)
|
|
147
|
-
>>> cat_reorder(c_ordered, x)
|
|
148
|
-
[a, b, b, c, c, c]
|
|
149
|
-
Categories (3, object): [b < c < a]
|
|
150
|
-
>>> cat_reorder(c + ['d'], x)
|
|
151
|
-
Traceback (most recent call last):
|
|
152
|
-
...
|
|
153
|
-
ValueError: Lengths are not equal. len(c) is 7 and len(x) is 6.
|
|
154
|
-
"""
|
|
155
|
-
if len(x) != len(y):
|
|
156
|
-
raise ValueError(f"Lengths are not equal. {len(x)=}, {len(x)=}")
|
|
157
|
-
summary = (
|
|
158
|
-
pd.Series(y)
|
|
159
|
-
.groupby(x, observed=True)
|
|
160
|
-
.apply(fun)
|
|
161
|
-
.sort_values(ascending=ascending)
|
|
162
|
-
)
|
|
163
|
-
cats = summary.index.to_list()
|
|
164
|
-
return pd.Categorical(x, categories=cats)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
# These are function that can be called by the user inside the aes()
|
|
168
|
-
# mapping. This is meant to make the variable transformations as easy
|
|
169
|
-
# as they are in ggplot2
|
|
170
|
-
AES_INNER_NAMESPACE = {"factor": pd.Categorical, "reorder": reorder}
|
|
171
|
-
|
|
172
|
-
|
|
173
115
|
def evaluate(
|
|
174
116
|
aesthetics: aes | dict[str, Any], data: pd.DataFrame, env: Environment
|
|
175
117
|
) -> pd.DataFrame:
|
|
@@ -207,7 +149,7 @@ def evaluate(
|
|
|
207
149
|
3 16
|
|
208
150
|
4 25
|
|
209
151
|
"""
|
|
210
|
-
env = env.with_outer_namespace(
|
|
152
|
+
env = env.with_outer_namespace(EVAL_ENVIRONMENT)
|
|
211
153
|
|
|
212
154
|
# Store evaluation results in a dict column in a dict
|
|
213
155
|
evaled = {}
|
plotnine/stats/stat_sina.py
CHANGED
|
@@ -57,6 +57,15 @@ class stat_sina(stat):
|
|
|
57
57
|
- `area` - Scale by the largest density/bin among the different sinas
|
|
58
58
|
- `count` - areas are scaled proportionally to the number of points
|
|
59
59
|
- `width` - Only scale according to the maxwidth parameter.
|
|
60
|
+
style :
|
|
61
|
+
Type of sina plot to draw. The options are
|
|
62
|
+
```python
|
|
63
|
+
'full' # Regular (2 sided)
|
|
64
|
+
'left' # Left-sided half
|
|
65
|
+
'right' # Right-sided half
|
|
66
|
+
'left-right' # Alternate (left first) half by the group
|
|
67
|
+
'right-left' # Alternate (right first) half by the group
|
|
68
|
+
```
|
|
60
69
|
|
|
61
70
|
See Also
|
|
62
71
|
--------
|
|
@@ -91,6 +100,7 @@ class stat_sina(stat):
|
|
|
91
100
|
"bin_limit": 1,
|
|
92
101
|
"random_state": None,
|
|
93
102
|
"scale": "area",
|
|
103
|
+
"style": "full",
|
|
94
104
|
}
|
|
95
105
|
CREATES = {"scaled"}
|
|
96
106
|
|
|
@@ -245,6 +255,29 @@ class stat_sina(stat):
|
|
|
245
255
|
|
|
246
256
|
def finish_layer(self, data, params):
|
|
247
257
|
# Rescale x in case positions have been adjusted
|
|
258
|
+
style = params["style"]
|
|
259
|
+
x_mean = data["x"].to_numpy()
|
|
248
260
|
x_mod = (data["xmax"] - data["xmin"]) / data["width"]
|
|
249
261
|
data["x"] = data["x"] + data["x_diff"] * x_mod
|
|
262
|
+
x = data["x"].to_numpy()
|
|
263
|
+
even = data["group"].to_numpy() % 2 == 0
|
|
264
|
+
|
|
265
|
+
def mirror_x(bool_idx):
|
|
266
|
+
"""
|
|
267
|
+
Mirror x locations along the mean value
|
|
268
|
+
"""
|
|
269
|
+
data.loc[bool_idx, "x"] = (
|
|
270
|
+
2 * x_mean[bool_idx] - data.loc[bool_idx, "x"]
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
match style:
|
|
274
|
+
case "left":
|
|
275
|
+
mirror_x(x_mean < x)
|
|
276
|
+
case "right":
|
|
277
|
+
mirror_x(x < x_mean)
|
|
278
|
+
case "left-right":
|
|
279
|
+
mirror_x(even & (x < x_mean) | ~even & (x_mean < x))
|
|
280
|
+
case "right-left":
|
|
281
|
+
mirror_x(even & (x_mean < x) | ~even & (x < x_mean))
|
|
282
|
+
|
|
250
283
|
return data
|
plotnine/themes/themeable.py
CHANGED
|
@@ -1080,7 +1080,7 @@ class axis_ticks_minor_x(MixinSequenceOfValues):
|
|
|
1080
1080
|
# to invisible. Theming should not change those artists to visible,
|
|
1081
1081
|
# so we return early.
|
|
1082
1082
|
params = ax.xaxis.get_tick_params(which="minor")
|
|
1083
|
-
if not params.get("
|
|
1083
|
+
if not params.get("bottom", False):
|
|
1084
1084
|
return
|
|
1085
1085
|
|
|
1086
1086
|
# We have to use both
|
|
@@ -21,9 +21,9 @@ plotnine/_mpl/transforms.py,sha256=DNaOlNq76xlT696sN8ot1bmYyp4mmrjXQHk3kTi4HIg,7
|
|
|
21
21
|
plotnine/_mpl/utils.py,sha256=c9wkxxUEweDg6O0hdgZonNOFncwxkqFye8_bL7td7I4,4131
|
|
22
22
|
plotnine/_mpl/layout_manager/__init__.py,sha256=IXpPF5Oycc45uFpK4MJ6kcQCe1u5VUfnHLNZGcnrJCg,157
|
|
23
23
|
plotnine/_mpl/layout_manager/_engine.py,sha256=ESUvbLAlZApFbBi6w7gZA7S4guS0Rmrj-us-gzYP2ZM,2809
|
|
24
|
-
plotnine/_mpl/layout_manager/_layout_items.py,sha256=
|
|
25
|
-
plotnine/_mpl/layout_manager/_layout_tree.py,sha256=
|
|
26
|
-
plotnine/_mpl/layout_manager/_spaces.py,sha256=
|
|
24
|
+
plotnine/_mpl/layout_manager/_layout_items.py,sha256=QcO6tiDhDvhU10Y1eBP1qQ5fZhPGEsbwLYmIftxXzkU,27007
|
|
25
|
+
plotnine/_mpl/layout_manager/_layout_tree.py,sha256=4B2WMhIYrbikG1P2K87KqB4PqbU40zxlOAiQwe6-FPU,19539
|
|
26
|
+
plotnine/_mpl/layout_manager/_spaces.py,sha256=k4Pm6rJZUvOEgCAsKiSPszIsvEkARyU5vWBX38s_KkA,32389
|
|
27
27
|
plotnine/_utils/__init__.py,sha256=9RVYfwi9NmDGGzprUlE40yv9tJhW_VDAZkUKTTuvN7g,32473
|
|
28
28
|
plotnine/_utils/context.py,sha256=HPQy_uyNXdS0s9URD7ZePyuc5hFU2XrRBLDTqRDLJzY,1708
|
|
29
29
|
plotnine/_utils/dev.py,sha256=0qgRbMhcd4dfuLuYxx0skocKAtfwHF02ntyILRBogbg,1629
|
|
@@ -69,11 +69,11 @@ plotnine/geoms/annotation_stripes.py,sha256=6LpfWzyAS6xpzTfIPorhZaVBL9j6jiSIoyVW
|
|
|
69
69
|
plotnine/geoms/geom.py,sha256=kn3ekM968Xx3nmRRQxI1sGcEl44LBv8E3m4nUC4njMI,16549
|
|
70
70
|
plotnine/geoms/geom_abline.py,sha256=Q9FNVIQMLr5Xq-fsGD8H17D6f8uOdOhGoDoMI-GTqU4,3178
|
|
71
71
|
plotnine/geoms/geom_area.py,sha256=wvQ4nNvhJNN3nfn6Bv1gCARC6IWTjOjOfHPfSmg6Sxc,818
|
|
72
|
-
plotnine/geoms/geom_bar.py,sha256
|
|
72
|
+
plotnine/geoms/geom_bar.py,sha256=SnqS4hPTfqXzdPh1U-kNuBg0LNX9_tQC9OKhIlB7cy0,1732
|
|
73
73
|
plotnine/geoms/geom_bin_2d.py,sha256=b2fAQVywug-ey3KtqeOoYQ2RNRSRE_fa4s7M41W-_FE,574
|
|
74
74
|
plotnine/geoms/geom_blank.py,sha256=au-WTJRdOcSfq_qQ6TugrSzjAweZfTElH9l8yYrmZ0I,821
|
|
75
75
|
plotnine/geoms/geom_boxplot.py,sha256=m9vrF4i4Cw_NGxZ9WLVPsDxbqQ3jJFtnJ-UrtE3teCI,9775
|
|
76
|
-
plotnine/geoms/geom_col.py,sha256=
|
|
76
|
+
plotnine/geoms/geom_col.py,sha256=sGZZzkXgwlgI2D-M1UE1QAWsWZqtP1z8R0HjrnW9hkY,1187
|
|
77
77
|
plotnine/geoms/geom_count.py,sha256=IRIlQqwt0kIHf9swYZbFqd5RSQCYRyFKm5hp1C9xHB4,511
|
|
78
78
|
plotnine/geoms/geom_crossbar.py,sha256=-TN5UohaMNYPjYavbvW9feyW_fR-CBxFqyvSSrPIYZQ,6237
|
|
79
79
|
plotnine/geoms/geom_density.py,sha256=UwUkJxI79L3z19tmoSI6ZYs418XTbRznd-Abzrec3VY,526
|
|
@@ -108,7 +108,7 @@ plotnine/geoms/geom_spoke.py,sha256=s-kug0H-YGhyjso9W43XvzJf9-g6inh8zzuSFeXzSaU,
|
|
|
108
108
|
plotnine/geoms/geom_step.py,sha256=oKi2lWG7M2lGpF4G0yC7_5qn5tb-gc3o4H-pu3_wS64,2364
|
|
109
109
|
plotnine/geoms/geom_text.py,sha256=CqmjsOJsAsIsTm6SkVJt0gms363AXpLN_JCTpuK6Krw,11908
|
|
110
110
|
plotnine/geoms/geom_tile.py,sha256=3x9BSxaSr-ys6N5R2wY8B9fNiyV9vMdoXbjIDqHy_ng,1431
|
|
111
|
-
plotnine/geoms/geom_violin.py,sha256
|
|
111
|
+
plotnine/geoms/geom_violin.py,sha256=VqwD2GiCRUCq5tLQbQVcLiXSaxWO-OMe9_K9GnWqdrs,7147
|
|
112
112
|
plotnine/geoms/geom_vline.py,sha256=qKUd4IosH1VrwHbqNbs29kZyIW5lQRa_LZLRcLbPg38,3377
|
|
113
113
|
plotnine/guides/__init__.py,sha256=ulI-mDhtq3jAQEAqPv8clrn3UHGFOu3xRuO7jXlq-LY,201
|
|
114
114
|
plotnine/guides/guide.py,sha256=L1O26atzSjiQR-YW9w3XEcH0BDFiqthdY6wA6DAOapo,8229
|
|
@@ -118,8 +118,9 @@ plotnine/guides/guide_legend.py,sha256=0CSyRxlHu0RSJjnMWvnStnq-EtjgdmHv54QgPWC3u
|
|
|
118
118
|
plotnine/guides/guides.py,sha256=UI3AhTOQCsXur_L-Jr3VwAQcX4dBeZMohSbA2wg1YeA,15478
|
|
119
119
|
plotnine/mapping/__init__.py,sha256=DLu9E0kwwuHxzTUenoVjCNTTdkWMwIDtkExLleBq1MI,205
|
|
120
120
|
plotnine/mapping/_env.py,sha256=ZzcSv54PLOD8b8Ny2h6xteGoO8bJdbj9dM6Mlg5h0V8,6094
|
|
121
|
-
plotnine/mapping/
|
|
122
|
-
plotnine/mapping/
|
|
121
|
+
plotnine/mapping/_eval_environment.py,sha256=ZPzIh61maJl79VQMj6ZsBGcaVV870SIARqI-Z4eNeXA,2463
|
|
122
|
+
plotnine/mapping/aes.py,sha256=fMwZJ7MhAcUqhUIb3ekmI_RVpKotJxCl66ale4P3B10,15797
|
|
123
|
+
plotnine/mapping/evaluation.py,sha256=kblTxVv3M4xIGnHyReUU0RtmmIN77Or2JBcci0nGGfE,5913
|
|
123
124
|
plotnine/plot_composition/__init__.py,sha256=ZJYpfVF158cQZ1zREXy6wHNJ4FbSmqWxIkHWZwX3QT8,148
|
|
124
125
|
plotnine/plot_composition/_compose.py,sha256=uQfXxDbEayqyjX6yMOCJiePqAK7I2OU5YnBhphkYFY4,11477
|
|
125
126
|
plotnine/plot_composition/_plotspec.py,sha256=0F7q7PjDMDqcallpnBdX3N2iSRjdBTyjSvMFf83uvPU,1015
|
|
@@ -175,7 +176,7 @@ plotnine/stats/stat_pointdensity.py,sha256=Lnf6vsqVEs9FxgnB-zk_LnzLfMBvH8MV4KQWN
|
|
|
175
176
|
plotnine/stats/stat_qq.py,sha256=aDjfUHqyBFFErBAJ5E9LJZU8SVBJbCnXSUl2-AJ3DQg,2876
|
|
176
177
|
plotnine/stats/stat_qq_line.py,sha256=_J6XvNXQwChhwl4dDgKl1czLnZhm04jXp9xwylsIwsU,3305
|
|
177
178
|
plotnine/stats/stat_quantile.py,sha256=D-TC-c2Ul0eFggz688X0l6MaqDrVmCBMM3aviiLTpHc,2481
|
|
178
|
-
plotnine/stats/stat_sina.py,sha256=
|
|
179
|
+
plotnine/stats/stat_sina.py,sha256=IAUxNoj001jctsSQaRiN81GpGFTeX35dqdWLMvnzIaA,9449
|
|
179
180
|
plotnine/stats/stat_smooth.py,sha256=hiJ5y7ai_qaFv8xxPeLs3BS6IPRvOy6Nor6SGA2H0wU,8016
|
|
180
181
|
plotnine/stats/stat_sum.py,sha256=CgxZlcwJ8t8gVojwSz55UEOKwDC0MfI5ISWrwDbqy4w,1655
|
|
181
182
|
plotnine/stats/stat_summary.py,sha256=RuuliRixo9c6sDnKmsQBXSJvfzkH2Oru7tyl88mpqzI,9289
|
|
@@ -199,7 +200,7 @@ plotnine/themes/theme_seaborn.py,sha256=l4lz5qD5CqhqBDgfQ61Mye3TX3f5aCHoHHx1Q04j
|
|
|
199
200
|
plotnine/themes/theme_tufte.py,sha256=qUOrZhQyfJgc0fmy8Es7tT7aYqUSzCjvkP7-dBilwHE,1926
|
|
200
201
|
plotnine/themes/theme_void.py,sha256=bU2REV9dI4x2kDIlPO6Oaq3E5di2cE8VDH-hRrnWEMc,3367
|
|
201
202
|
plotnine/themes/theme_xkcd.py,sha256=q3i1W97kBwpCRbR_Y609JxcfJA2cEX5e5iAS7flbF6I,2257
|
|
202
|
-
plotnine/themes/themeable.py,sha256=
|
|
203
|
+
plotnine/themes/themeable.py,sha256=kYrk9Bq4274BD5cz1fGRgkdDwrvKhznrbhViXxl0PG0,69181
|
|
203
204
|
plotnine/themes/elements/__init__.py,sha256=Z9xHdhyWPNR2uF_P80aBEXYWp1DU-T2KGOuM7VimpbM,295
|
|
204
205
|
plotnine/themes/elements/element_base.py,sha256=D7cfEglzsSuhW91KpZVAZ2MAHWZp64r9Aajoh8uMGZ4,832
|
|
205
206
|
plotnine/themes/elements/element_blank.py,sha256=4r7-6HeR1494oWNIGQh0ASrFQ4SLvYa6aQHA85eH-Ds,187
|
|
@@ -207,8 +208,8 @@ plotnine/themes/elements/element_line.py,sha256=xF6xW-iA66YEP_fN7ooqaYry8_8qZT-e
|
|
|
207
208
|
plotnine/themes/elements/element_rect.py,sha256=w5cLH-Sr4cTRXVdkRiu8kBqFt3TXHhIb1MUITfi89gE,1767
|
|
208
209
|
plotnine/themes/elements/element_text.py,sha256=8yhwBa9s9JKCtBcqcBNybbCGK6ieDnZv4SHiC4Sy2qc,6255
|
|
209
210
|
plotnine/themes/elements/margin.py,sha256=EsT46lqky7APHxMUDiNiTieNo_SIbHF-Sjhmf9zo4WY,2880
|
|
210
|
-
plotnine-0.15.0.
|
|
211
|
-
plotnine-0.15.0.
|
|
212
|
-
plotnine-0.15.0.
|
|
213
|
-
plotnine-0.15.0.
|
|
214
|
-
plotnine-0.15.0.
|
|
211
|
+
plotnine-0.15.0.dev3.dist-info/licenses/LICENSE,sha256=GY4tQiUd17Tq3wWR42Zs9MRTFOTf6ahIXhZTcwAdOeU,1082
|
|
212
|
+
plotnine-0.15.0.dev3.dist-info/METADATA,sha256=0Cwjkbtul-pvjlRpBQzRcLgAbr4G9rI3NLSu6sLhtgA,9289
|
|
213
|
+
plotnine-0.15.0.dev3.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
214
|
+
plotnine-0.15.0.dev3.dist-info/top_level.txt,sha256=t340Mbko1ZbmvYPkQ81dIiPHcaQdTUszYz-bWUpr8ys,9
|
|
215
|
+
plotnine-0.15.0.dev3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|