marsilea 0.3.0rc2__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. {marsilea-0.3.0rc2 → marsilea-0.3.2}/.github/workflows/build.yaml +4 -4
  2. {marsilea-0.3.0rc2 → marsilea-0.3.2}/PKG-INFO +1 -1
  3. {marsilea-0.3.0rc2 → marsilea-0.3.2}/examples/plot_seqalign.py +2 -2
  4. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/__init__.py +1 -1
  5. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/_deform.py +59 -9
  6. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/base.py +49 -28
  7. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/dataset.py +1 -0
  8. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/dendrogram.py +37 -7
  9. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/plotter/arc.py +2 -2
  10. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/plotter/bar.py +6 -2
  11. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/plotter/text.py +74 -8
  12. {marsilea-0.3.0rc2 → marsilea-0.3.2}/readthedocs.yaml +1 -2
  13. {marsilea-0.3.0rc2 → marsilea-0.3.2}/.github/workflows/lint.yaml +0 -0
  14. {marsilea-0.3.0rc2 → marsilea-0.3.2}/.gitignore +0 -0
  15. {marsilea-0.3.0rc2 → marsilea-0.3.2}/.streamlit/config.toml +0 -0
  16. {marsilea-0.3.0rc2 → marsilea-0.3.2}/LICENSE +0 -0
  17. {marsilea-0.3.0rc2 → marsilea-0.3.2}/README.md +0 -0
  18. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/.gitignore +0 -0
  19. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/Home.py +0 -0
  20. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/components/__init__.py +0 -0
  21. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/components/cmap_selector/__init__.py +0 -0
  22. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/components/cmap_selector/selector.py +0 -0
  23. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/components/data_input.py +0 -0
  24. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/components/example_download.py +0 -0
  25. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/components/initialize.py +0 -0
  26. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/components/main_plots.py +0 -0
  27. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/components/resource.py +0 -0
  28. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/components/saver.py +0 -0
  29. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/components/side_plots.py +0 -0
  30. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/components/state.py +0 -0
  31. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/components/transformation.py +0 -0
  32. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/components/upset_example.csv +0 -0
  33. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/V1.mp4 +0 -0
  34. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/V2.mp4 +0 -0
  35. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/annolabels.png +0 -0
  36. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/bar.png +0 -0
  37. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/box.png +0 -0
  38. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/boxen.png +0 -0
  39. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/colors.png +0 -0
  40. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/dendrogram.svg +0 -0
  41. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/favicon.png +0 -0
  42. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/favicon.svg +0 -0
  43. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/heatmap.png +0 -0
  44. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/heatmap.svg +0 -0
  45. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/logo.png +0 -0
  46. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/mark_map.png +0 -0
  47. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/mark_map.svg +0 -0
  48. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/point.png +0 -0
  49. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/sized_heatmap.png +0 -0
  50. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/sized_heatmap.svg +0 -0
  51. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/sized_onlymap.png +0 -0
  52. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/strip.png +0 -0
  53. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/swarm.png +0 -0
  54. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/violin.png +0 -0
  55. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/img/x-layout.png +0 -0
  56. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/pages/01 /360/237/237/250 Simple_Heatmap.py" +0 -0
  57. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/pages/02 /360/237/215/261 X-Layout_Heatmap.py" +0 -0
  58. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/pages/03 /360/237/225/270/357/270/217 Upsetplot.py" +0 -0
  59. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/pages/04 /360/237/223/232 Manual.py" +0 -0
  60. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/requirements.txt +0 -0
  61. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/script/heatmap_data.py +0 -0
  62. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/script/plot_layer_example_images.py +0 -0
  63. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/script/plotter_example_images.py +0 -0
  64. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/set_data_memberships.txt +0 -0
  65. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/test.xlsx +0 -0
  66. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/test_data/Stats_plot.txt +0 -0
  67. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/test_data/example_data.txt +0 -0
  68. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/test_data/number.txt +0 -0
  69. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/test_data/row_labels.txt +0 -0
  70. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/test_data/set_data_memberships.txt +0 -0
  71. {marsilea-0.3.0rc2 → marsilea-0.3.2}/app/upset.xlsx +0 -0
  72. {marsilea-0.3.0rc2 → marsilea-0.3.2}/data/IMDB.csv +0 -0
  73. {marsilea-0.3.0rc2 → marsilea-0.3.2}/data/PATIENT_DATA_oncoprint.tsv +0 -0
  74. {marsilea-0.3.0rc2 → marsilea-0.3.2}/data/pbmc3k.h5ad +0 -0
  75. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/Makefile +0 -0
  76. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/README.md +0 -0
  77. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/make.bat +0 -0
  78. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/requirements.txt +0 -0
  79. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/.gitignore +0 -0
  80. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/_static/css/custom.css +0 -0
  81. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/_templates/autosummary.rst +0 -0
  82. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/api/base.rst +0 -0
  83. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/api/index.rst +0 -0
  84. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/api/plots.rst +0 -0
  85. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/api/viz.rst +0 -0
  86. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/conf.py +0 -0
  87. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/how-to.rst +0 -0
  88. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/img/marsilea-renderplan-logic.drawio.svg +0 -0
  89. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/index.rst +0 -0
  90. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/tutorial/axes-level.rst +0 -0
  91. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/tutorial/concatenation.rst +0 -0
  92. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/tutorial/heatmap.rst +0 -0
  93. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/tutorial/index.rst +0 -0
  94. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/tutorial/intro.rst +0 -0
  95. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/tutorial/intro2.rst +0 -0
  96. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/tutorial/new_renderplan.rst +0 -0
  97. {marsilea-0.3.0rc2 → marsilea-0.3.2}/docs/source/tutorial/upset.rst +0 -0
  98. {marsilea-0.3.0rc2 → marsilea-0.3.2}/examples/.gitignore +0 -0
  99. {marsilea-0.3.0rc2 → marsilea-0.3.2}/examples/README.rst +0 -0
  100. {marsilea-0.3.0rc2 → marsilea-0.3.2}/examples/plot_arc_diagram.py +0 -0
  101. {marsilea-0.3.0rc2 → marsilea-0.3.2}/examples/plot_bivariate_dist.py +0 -0
  102. {marsilea-0.3.0rc2 → marsilea-0.3.2}/examples/plot_mouse_embryo.py +0 -0
  103. {marsilea-0.3.0rc2 → marsilea-0.3.2}/examples/plot_oil_well.py +0 -0
  104. {marsilea-0.3.0rc2 → marsilea-0.3.2}/examples/plot_oncoprint.py +0 -0
  105. {marsilea-0.3.0rc2 → marsilea-0.3.2}/examples/plot_pbmc3k.py +0 -0
  106. {marsilea-0.3.0rc2 → marsilea-0.3.2}/examples/plot_stacked_bar.py +0 -0
  107. {marsilea-0.3.0rc2 → marsilea-0.3.2}/examples/plot_upset.py +0 -0
  108. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/Logo(1).zip +0 -0
  109. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/Logo.zip +0 -0
  110. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/banner-blue.jpg +0 -0
  111. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/banner-dark-gradient.jpg +0 -0
  112. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/banner-dark.jpg +0 -0
  113. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/banner-rice-white.jpg +0 -0
  114. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/banner-white.jpg +0 -0
  115. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/logo/Logo/.DS_Store +0 -0
  116. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/logo/Logo/Logo.svg +0 -0
  117. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/logo/Logo/logo.png +0 -0
  118. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/logo/Logo//346/265/205/350/223/235/346/270/220/345/217/230.jpg" +0 -0
  119. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/logo/Logo//346/267/261/350/211/262/350/203/214/346/231/257.jpg" +0 -0
  120. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/logo/Logo//347/261/263/350/211/262.jpg" +0 -0
  121. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/logo/Logo//347/272/257/347/231/275.jpg" +0 -0
  122. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/logo/Logo//350/247/222/345/272/246/346/270/220/345/217/230.jpg" +0 -0
  123. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/logo/__MACOSX/Logo/._.DS_Store +0 -0
  124. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/logo.png +0 -0
  125. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/logo.svg +0 -0
  126. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/Snap-1.png +0 -0
  127. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/Snap-2.png +0 -0
  128. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/Snap-3.png +0 -0
  129. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/Stacked bar.png +0 -0
  130. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/bivar_kde.png +0 -0
  131. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/layout-skeleton-step1.png +0 -0
  132. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/layout-skeleton-step2.png +0 -0
  133. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/layout-skeleton-step3.png +0 -0
  134. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/layout-skeleton-step4.png +0 -0
  135. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/layout-step-1a.png +0 -0
  136. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/layout-step-1b.png +0 -0
  137. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/layout-step-2.png +0 -0
  138. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/layout-step-3.png +0 -0
  139. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/layout-step-4.png +0 -0
  140. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/oncoprint.png +0 -0
  141. {marsilea-0.3.0rc2 → marsilea-0.3.2}/img/publication/upset.png +0 -0
  142. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/_api.py +0 -0
  143. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/exceptions.py +0 -0
  144. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/heatmap.py +0 -0
  145. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/layers.py +0 -0
  146. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/layout.py +0 -0
  147. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/plotter/__init__.py +0 -0
  148. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/plotter/_images.py +0 -0
  149. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/plotter/_seaborn.py +0 -0
  150. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/plotter/_utils.py +0 -0
  151. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/plotter/base.py +0 -0
  152. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/plotter/bio.py +0 -0
  153. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/plotter/mesh.py +0 -0
  154. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/upset.py +0 -0
  155. {marsilea-0.3.0rc2 → marsilea-0.3.2}/marsilea/utils.py +0 -0
  156. {marsilea-0.3.0rc2 → marsilea-0.3.2}/pyproject.toml +0 -0
  157. {marsilea-0.3.0rc2 → marsilea-0.3.2}/scripts/logo.py +0 -0
  158. {marsilea-0.3.0rc2 → marsilea-0.3.2}/scripts/pub_figure.py +0 -0
  159. {marsilea-0.3.0rc2 → marsilea-0.3.2}/scripts/pub_figures/bivariate_kde.py +0 -0
  160. {marsilea-0.3.0rc2 → marsilea-0.3.2}/scripts/pub_figures/layout-skeleton-step.py +0 -0
  161. {marsilea-0.3.0rc2 → marsilea-0.3.2}/scripts/pub_figures/layout-step.py +0 -0
  162. {marsilea-0.3.0rc2 → marsilea-0.3.2}/scripts/pub_figures/onco.py +0 -0
  163. {marsilea-0.3.0rc2 → marsilea-0.3.2}/scripts/pub_figures/stacked_bar.py +0 -0
  164. {marsilea-0.3.0rc2 → marsilea-0.3.2}/scripts/pub_figures/upset.py +0 -0
  165. {marsilea-0.3.0rc2 → marsilea-0.3.2}/scripts/x-layout.py +0 -0
  166. {marsilea-0.3.0rc2 → marsilea-0.3.2}/setup.py +0 -0
  167. {marsilea-0.3.0rc2 → marsilea-0.3.2}/tests/test_data_input.py +0 -0
  168. {marsilea-0.3.0rc2 → marsilea-0.3.2}/tests/test_deform.py +0 -0
  169. {marsilea-0.3.0rc2 → marsilea-0.3.2}/tests/test_mesh.py +0 -0
@@ -57,14 +57,14 @@ jobs:
57
57
  - name: Publish to test.ipynb pypi
58
58
  env:
59
59
  FLIT_INDEX_URL: https://test.ipynb.pypi.org/legacy/
60
- FLIT_USERNAME: ${{ secrets.PYPI_USERNAME }}
61
- FLIT_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
60
+ FLIT_USERNAME: __token__
61
+ FLIT_PASSWORD: ${{ secrets.PYPI_TOKEN }}
62
62
  run: flit publish || exit 0
63
63
 
64
64
  - name: Publish to pypi
65
65
  if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v')
66
66
  env:
67
67
  FLIT_INDEX_URL: https://upload.pypi.org/legacy/
68
- FLIT_USERNAME: ${{ secrets.PYPI_USERNAME }}
69
- FLIT_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
68
+ FLIT_USERNAME: __token__
69
+ FLIT_PASSWORD: ${{ secrets.PYPI_TOKEN }}
70
70
  run: flit publish
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: marsilea
3
- Version: 0.3.0rc2
3
+ Version: 0.3.2
4
4
  Summary: Create x-layout visualization
5
5
  Author: Zhihang Zheng
6
6
  Author-email: Mr-Milk <yb97643@um.edu.mo>
@@ -1,6 +1,6 @@
1
1
  """
2
- DNA Sequence Alignment Plot
3
- ===========================
2
+ Sequence Alignment Plot
3
+ =======================
4
4
  """
5
5
  from collections import Counter
6
6
  import numpy as np
@@ -1,6 +1,6 @@
1
1
  """Create x-layout visualization"""
2
2
 
3
- __version__ = "0.3.0rc2"
3
+ __version__ = "0.3.2"
4
4
 
5
5
  from ._deform import Deformation
6
6
  from .base import WhiteBoard, ClusterBoard
@@ -1,3 +1,5 @@
1
+ from typing import Mapping
2
+
1
3
  import numpy as np
2
4
 
3
5
  from .dendrogram import Dendrogram, GroupDendrogram
@@ -25,9 +27,13 @@ class Deformation:
25
27
 
26
28
  row_breakpoints = None
27
29
  col_breakpoints = None
30
+ row_split_order = None
31
+ col_split_order = None
28
32
 
29
33
  row_dendrogram = None
30
34
  col_dendrogram = None
35
+ row_linkage = None # User supplied linkage
36
+ col_linkage = None
31
37
 
32
38
  row_reorder_index = None
33
39
  col_reorder_index = None
@@ -73,17 +79,20 @@ class Deformation:
73
79
  self.data_col_reindex = reindex
74
80
  self._col_clustered = False
75
81
 
76
- def set_cluster(self, col=None, row=None, use_meta=True, **kwargs):
82
+ def set_cluster(self, col=None, row=None, use_meta=True,
83
+ linkage=None, **kwargs):
77
84
  if col is not None:
78
85
  self.is_col_cluster = col
79
86
  self.col_cluster_kws = kwargs
80
87
  self._col_clustered = False
81
88
  self._use_col_meta = use_meta
89
+ self.col_linkage = linkage
82
90
  if row is not None:
83
91
  self.is_row_cluster = row
84
92
  self.row_cluster_kws = kwargs
85
93
  self._row_clustered = False
86
94
  self._use_row_meta = use_meta
95
+ self.row_linkage = linkage
87
96
 
88
97
  def get_data(self):
89
98
  data = self.data
@@ -93,17 +102,23 @@ class Deformation:
93
102
  data = data[:, self.data_col_reindex]
94
103
  return data
95
104
 
96
- def set_split_row(self, breakpoints=None):
105
+ def set_split_row(self, breakpoints=None, order=None):
97
106
  if breakpoints is not None:
98
107
  self.is_row_split = True
99
108
  self.row_breakpoints = [0, *np.sort(np.asarray(breakpoints)),
100
109
  self._nrow]
110
+ if order is None:
111
+ order = np.arange(len(breakpoints) + 1)
112
+ self.row_split_order = order
101
113
 
102
- def set_split_col(self, breakpoints=None):
114
+ def set_split_col(self, breakpoints=None, order=None):
103
115
  if breakpoints is not None:
104
116
  self.is_col_split = True
105
117
  self.col_breakpoints = [0, *np.sort(np.asarray(breakpoints)),
106
118
  self._ncol]
119
+ if order is None:
120
+ order = np.arange(len(breakpoints) + 1)
121
+ self.col_split_order = order
107
122
 
108
123
  @property
109
124
  def row_ratios(self):
@@ -169,11 +184,24 @@ class Deformation:
169
184
  return self.split_by_col(data)
170
185
  return data
171
186
 
187
+ _linkage_check_msg = ("If you want to specific linkage when splitting, "
188
+ "it must be a dict-like object, "
189
+ "with keys as group names and values as linkage")
190
+
172
191
  def cluster_row(self):
173
192
  row_data = self.split_by_row(self.get_data())
174
193
  if self.is_row_split:
175
- dens = [Dendrogram(
176
- chunk, **self.row_cluster_kws) for chunk in row_data]
194
+ if not (isinstance(self.row_linkage, Mapping) or (self.row_linkage is None)):
195
+ raise TypeError(self._linkage_check_msg)
196
+ dens = []
197
+ for chunk, k in zip(row_data, self.row_split_order):
198
+ linkage = None
199
+ if self.row_linkage is not None:
200
+ linkage = self.row_linkage.get(k)
201
+ if linkage is None:
202
+ raise KeyError(f"Linkage for group {k} is not specified")
203
+ dens.append(Dendrogram(chunk, linkage=linkage, key=k, **self.row_cluster_kws))
204
+
177
205
  dg = GroupDendrogram(dens, **self.row_cluster_kws)
178
206
  if self._use_row_meta:
179
207
  self.row_chunk_index = dg.reorder_index
@@ -181,15 +209,23 @@ class Deformation:
181
209
  self.row_chunk_index = np.arange(len(dens))
182
210
  self.row_reorder_index = [d.reorder_index for d in dens]
183
211
  else:
184
- dg = Dendrogram(row_data, **self.row_cluster_kws)
212
+ dg = Dendrogram(row_data, linkage=self.row_linkage, **self.row_cluster_kws)
185
213
  self.row_reorder_index = dg.reorder_index
186
214
  self.row_dendrogram = dg
187
215
 
188
216
  def cluster_col(self):
189
217
  col_data = self.split_by_col(self.get_data())
190
218
  if self.is_col_split:
191
- dens = [Dendrogram(
192
- chunk.T, **self.col_cluster_kws) for chunk in col_data]
219
+ if not (isinstance(self.col_linkage, Mapping) or (self.col_linkage is None)):
220
+ raise TypeError(self._linkage_check_msg)
221
+ dens = []
222
+ for chunk, k in zip(col_data, self.col_split_order):
223
+ linkage = None
224
+ if self.col_linkage is not None:
225
+ linkage = self.col_linkage.get(k)
226
+ if linkage is None:
227
+ raise KeyError(f"Linkage for group {k} is not specified")
228
+ dens.append(Dendrogram(chunk.T, linkage=linkage, key=k, **self.col_cluster_kws))
193
229
  dg = GroupDendrogram(dens, **self.col_cluster_kws)
194
230
  if self._use_col_meta:
195
231
  self.col_chunk_index = dg.reorder_index
@@ -197,7 +233,7 @@ class Deformation:
197
233
  self.col_chunk_index = np.arange(len(dens))
198
234
  self.col_reorder_index = [d.reorder_index for d in dens]
199
235
  else:
200
- dg = Dendrogram(col_data.T, **self.col_cluster_kws)
236
+ dg = Dendrogram(col_data.T, linkage=self.col_linkage, **self.col_cluster_kws)
201
237
  self.col_reorder_index = dg.reorder_index
202
238
  self.col_dendrogram = dg
203
239
 
@@ -340,6 +376,20 @@ class Deformation:
340
376
  self._run_cluster()
341
377
  return self.col_dendrogram
342
378
 
379
+ def get_row_linkage(self):
380
+ if self.row_dendrogram is not None:
381
+ if self.is_row_split:
382
+ return {x.key: x.Z for x in self.row_dendrogram.orig_dens}
383
+ else:
384
+ return self.row_dendrogram.Z
385
+
386
+ def get_col_linkage(self):
387
+ if self.col_dendrogram is not None:
388
+ if self.is_col_split:
389
+ return {x.key: x.Z for x in self.col_dendrogram.orig_dens}
390
+ else:
391
+ return self.col_dendrogram.Z
392
+
343
393
  @property
344
394
  def is_split(self):
345
395
  return self.is_row_split | self.is_col_split
@@ -1,16 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
- import numpy as np
4
3
  import warnings
5
4
  from copy import deepcopy
5
+ from numbers import Number
6
+ from typing import List, Dict
7
+ from uuid import uuid4
8
+
9
+ import numpy as np
6
10
  from legendkit.layout import vstack, hstack
7
11
  from matplotlib import pyplot as plt
8
12
  from matplotlib.artist import Artist
9
13
  from matplotlib.colors import is_color_like
10
14
  from matplotlib.figure import Figure
11
- from numbers import Number
12
- from typing import List, Dict
13
- from uuid import uuid4
14
15
 
15
16
  from ._deform import Deformation
16
17
  from .dendrogram import Dendrogram
@@ -26,14 +27,13 @@ def reorder_index(arr, order=None):
26
27
  for ix, a in enumerate(arr):
27
28
  indices[a].append(ix)
28
29
 
30
+ if order is None:
31
+ order = sorted(uniq)
32
+
29
33
  final_index = []
30
- if order is not None:
31
- for it in order:
32
- final_index += indices[it]
33
- else:
34
- for it in indices.values():
35
- final_index += it
36
- return final_index
34
+ for it in order:
35
+ final_index += indices[it]
36
+ return final_index, order
37
37
 
38
38
 
39
39
  def get_breakpoints(arr):
@@ -521,11 +521,11 @@ class ClusterBoard(WhiteBoard):
521
521
  self._cluster_data = cluster_data
522
522
  self._deform = Deformation(cluster_data)
523
523
 
524
- def add_dendrogram(self, side, method=None, metric=None,
524
+ def add_dendrogram(self, side, method=None, metric=None, linkage=None,
525
525
  add_meta=True, add_base=True, add_divider=True,
526
526
  meta_color=None, linewidth=None, colors=None,
527
527
  divider_style="--", meta_ratio=.2,
528
- show=True, name=None, size=0.5, pad=0.):
528
+ show=True, name=None, size=0.5, pad=0., get_meta_center=None):
529
529
  """Run cluster and add dendrogram
530
530
 
531
531
  .. note::
@@ -542,11 +542,17 @@ class ClusterBoard(WhiteBoard):
542
542
  See scipy's :meth:`linkage <scipy.cluster.hierarchy.linkage>`
543
543
  metric : str
544
544
  See scipy's :meth:`linkage <scipy.cluster.hierarchy.linkage>`
545
- add_meta : bool
545
+ linkage : ndarray
546
+ Precomputed linkage matrix.
547
+ See scipy's :meth:`linkage <scipy.cluster.hierarchy.linkage>` for
548
+ specific format.
549
+ add_meta : None | bool
550
+ By default, add_meta is set to False if the linkage is provided, otherwise True.
546
551
  If the data is split, a meta dendrogram can be drawn for data
547
552
  chunks. The mean value of the data chunk is used to calculate
548
553
  linkage matrix for meta dendrogram.
549
- add_base : bool
554
+ add_base : None | bool
555
+ By default, add_meta is set to False if the linkage is provided, otherwise True.
550
556
  Draw the base dendrogram for each data chunk. You can turn this
551
557
  off if the base dendrogram is too crowded.
552
558
  add_divider : bool
@@ -571,6 +577,11 @@ class ClusterBoard(WhiteBoard):
571
577
  The name of the dendrogram axes
572
578
  size : float
573
579
  pad : float
580
+ get_meta_center: callable
581
+ A function to calculate the centroid of data. It should accept a 2D numpy
582
+ array as input and return a 1D numpy array of the same length as the number
583
+ of columns in the input, representing the centroid. The default will use the
584
+ mean values.
574
585
 
575
586
  Examples
576
587
  --------
@@ -638,46 +649,50 @@ class ClusterBoard(WhiteBoard):
638
649
  den_options['pos'] = "row"
639
650
  self._row_den.append(den_options)
640
651
  deform.set_cluster(row=True, method=method, metric=metric,
641
- use_meta=add_meta)
652
+ linkage=linkage, use_meta=add_meta,
653
+ get_meta_center=get_meta_center)
642
654
  else:
643
655
  den_options['pos'] = "col"
644
656
  self._col_den.append(den_options)
645
657
  deform.set_cluster(col=True, method=method, metric=metric,
646
- use_meta=add_meta)
647
-
658
+ linkage=linkage, use_meta=add_meta,
659
+ get_meta_center=get_meta_center)
660
+
648
661
  def hsplit(self, cut=None, labels=None, order=None, spacing=0.01):
649
662
  if self._split_row:
650
663
  raise SplitTwice(axis="horizontally")
651
664
  self._split_row = True
652
665
 
653
- self._deform.hspace = spacing
666
+ deform = self.get_deform()
667
+ deform.hspace = spacing
654
668
  if cut is not None:
655
- self._deform.set_split_row(breakpoints=cut)
669
+ deform.set_split_row(breakpoints=cut)
656
670
  else:
657
671
  labels = np.asarray(labels)
658
672
 
659
- reindex = reorder_index(labels, order=order)
660
- self._deform.set_data_row_reindex(reindex)
673
+ reindex, order = reorder_index(labels, order=order)
674
+ deform.set_data_row_reindex(reindex)
661
675
 
662
676
  breakpoints = get_breakpoints(labels[reindex])
663
- self._deform.set_split_row(breakpoints=breakpoints)
677
+ deform.set_split_row(breakpoints=breakpoints, order=order)
664
678
 
665
679
  def vsplit(self, cut=None, labels=None, order=None, spacing=0.01):
666
680
  if self._split_col:
667
681
  raise SplitTwice(axis="vertically")
668
682
  self._split_col = True
669
683
 
670
- self._deform.wspace = spacing
684
+ deform = self.get_deform()
685
+ deform.wspace = spacing
671
686
  if cut is not None:
672
- self._deform.set_split_col(breakpoints=cut)
687
+ deform.set_split_col(breakpoints=cut)
673
688
  else:
674
689
  labels = np.asarray(labels)
675
690
 
676
- reindex = reorder_index(labels, order=order)
677
- self._deform.set_data_col_reindex(reindex)
691
+ reindex, order = reorder_index(labels, order=order)
692
+ deform.set_data_col_reindex(reindex)
678
693
 
679
694
  breakpoints = get_breakpoints(labels[reindex])
680
- self._deform.set_split_col(breakpoints=breakpoints)
695
+ deform.set_split_col(breakpoints=breakpoints, order=order)
681
696
 
682
697
  def _setup_axes(self):
683
698
  deform = self.get_deform()
@@ -768,6 +783,12 @@ class ClusterBoard(WhiteBoard):
768
783
  def get_deform(self):
769
784
  return self._deform
770
785
 
786
+ def get_row_linkage(self):
787
+ return self._deform.get_row_linkage()
788
+
789
+ def get_col_linkage(self):
790
+ return self._deform.get_col_linkage()
791
+
771
792
  @property
772
793
  def row_cluster(self):
773
794
  return len(self._row_den) > 0
@@ -16,6 +16,7 @@ def load_data(name, cache=True):
16
16
  (TCGA, PanCancer Atlas)
17
17
  - 'mouse_embryo': Spatial mapping of mouse embryo at E12.5
18
18
  - 'seq_align': Sequence alignment data
19
+ - 'les_miserables': Characters in Les Miserables
19
20
 
20
21
  Parameters
21
22
  ----------
@@ -3,7 +3,7 @@ from itertools import cycle
3
3
  from matplotlib.collections import LineCollection
4
4
  from matplotlib.colors import is_color_like
5
5
  from matplotlib.lines import Line2D
6
- from scipy.cluster.hierarchy import linkage, dendrogram
6
+ from scipy.cluster.hierarchy import linkage as scipy_linkage, dendrogram
7
7
  from typing import List, Sequence
8
8
 
9
9
 
@@ -15,7 +15,12 @@ class _DendrogramBase:
15
15
  data,
16
16
  method=None,
17
17
  metric=None,
18
+ linkage=None,
19
+ get_meta_center=None,
20
+ key=None,
18
21
  ):
22
+ self.key = key
23
+ self.data = data
19
24
  if method is None:
20
25
  method = "single"
21
26
  if metric is None:
@@ -29,7 +34,10 @@ class _DendrogramBase:
29
34
  self._reorder_index = np.array([0])
30
35
  self.is_singleton = True
31
36
  else:
32
- self.Z = linkage(data, method=method, metric=metric)
37
+ if linkage is not None:
38
+ self.Z = linkage
39
+ else:
40
+ self.Z = scipy_linkage(data, method=method, metric=metric)
33
41
  self._plot_data = dendrogram(self.Z, no_plot=True)
34
42
 
35
43
  self.x_coords = np.asarray(self._plot_data['icoord']) / 5
@@ -59,8 +67,22 @@ class _DendrogramBase:
59
67
  self.ylim = np.array([0, self.max_dependent_coord * 1.05])
60
68
  self._render_xlim = self.xlim
61
69
  self._render_ylim = self.ylim
70
+
62
71
  # Should be lazy eval
63
- self._center = np.mean(data, axis=0)
72
+ # TODO: Allow center to be calculated differently
73
+ if get_meta_center is None:
74
+ self._center = np.mean(data, axis=0)
75
+ elif callable(get_meta_center):
76
+ # Ensure the centroid function returns a numpy array of correct shape
77
+ centroid = get_meta_center(data)
78
+ if isinstance(centroid, np.ndarray) and centroid.shape == data.shape[1:]:
79
+ self._center = centroid
80
+ else:
81
+ raise ValueError(
82
+ "The get_meta_center must return a numpy array with shape "
83
+ "matching the number of features in the data.")
84
+ else:
85
+ raise TypeError("The get_meta_center must be a callable function or None.")
64
86
 
65
87
  @property
66
88
  def xrange(self):
@@ -143,9 +165,13 @@ class Dendrogram(_DendrogramBase):
143
165
  def __init__(self,
144
166
  data: np.ndarray,
145
167
  method=None,
146
- metric=None
168
+ metric=None,
169
+ linkage=None,
170
+ get_meta_center=None,
171
+ key=None,
147
172
  ):
148
- super().__init__(data, method=method, metric=metric)
173
+ super().__init__(data, method=method, metric=metric, key=key,
174
+ linkage=linkage, get_meta_center=get_meta_center)
149
175
 
150
176
  # here we left an empty **kwargs to align api with GroupDendrogram
151
177
  def draw(self, ax, orient="top",
@@ -223,10 +249,14 @@ class GroupDendrogram(_DendrogramBase):
223
249
  def __init__(self,
224
250
  dens: List[Dendrogram],
225
251
  method=None,
226
- metric=None
252
+ metric=None,
253
+ get_meta_center=None,
254
+ key=None,
255
+ **kwargs,
227
256
  ):
228
257
  data = np.vstack([d.center for d in dens])
229
- super().__init__(data, method=method, metric=metric)
258
+ super().__init__(data, method=method, metric=metric,
259
+ get_meta_center=get_meta_center, key=key)
230
260
  self.orig_dens = np.asarray(dens)
231
261
  self.dens = np.asarray(dens)[self.reorder_index]
232
262
  self.n = len(self.dens)
@@ -210,8 +210,8 @@ class Arc(StatsBase):
210
210
  xy = (arc_mid, 0)
211
211
  angle = 0
212
212
  sizes.append(arc_width)
213
- arc = mArc(xy, arc_width, arc_width * 2, angle,
214
- **options)
213
+ arc = mArc(xy, arc_width, arc_width * 2,
214
+ angle=angle, **options)
215
215
  ax.add_patch(arc)
216
216
 
217
217
  lim = np.max(sizes)
@@ -387,6 +387,10 @@ class StackBar(_BarBase):
387
387
  if self.side == "left":
388
388
  ax.invert_xaxis()
389
389
 
390
+ # Hanlde data
391
+ if orient == "h":
392
+ data = data[:, ::-1]
393
+
390
394
  locs = np.arange(0, lim) + 0.5
391
395
  bottom = np.zeros(lim)
392
396
 
@@ -395,8 +399,8 @@ class StackBar(_BarBase):
395
399
  labels = self.labels[::-1]
396
400
  else:
397
401
  labels = [None for _ in range(len(data))]
398
- colors = self.bar_colors[:len(data)][::-1]
399
- for ix, row in enumerate(data[::-1]):
402
+ colors = self.bar_colors[:len(data)]
403
+ for ix, row in enumerate(data):
400
404
  bars = bar(locs, row, self.width, bottom,
401
405
  fc=colors[ix],
402
406
  label=labels[ix], **self.options)
@@ -668,7 +668,8 @@ class Labels(_LabelBase):
668
668
  coords = self.get_axes_coords(data)
669
669
  params = self.get_text_params()
670
670
  if self.texts_size is not None:
671
- offset_ratio = self.padding / 72 / self.texts_size
671
+ padding_px = self.padding / 72
672
+ offset_ratio = padding_px / (self.texts_size + padding_px)
672
673
  else:
673
674
  offset_ratio = 0
674
675
 
@@ -834,6 +835,7 @@ class _ChunkBase(_LabelBase):
834
835
 
835
836
  def __init__(self, texts,
836
837
  fill_colors=None,
838
+ align=None,
837
839
  props=None, padding=2, bordercolor=None,
838
840
  borderwidth=None, borderstyle=None,
839
841
  **options):
@@ -841,6 +843,7 @@ class _ChunkBase(_LabelBase):
841
843
  n = len(texts)
842
844
  self.n = n
843
845
  self.texts = texts
846
+ self.align = align
844
847
  self.padding = padding
845
848
 
846
849
  if is_color_like(fill_colors):
@@ -876,6 +879,21 @@ class _ChunkBase(_LabelBase):
876
879
  super().__init__()
877
880
  self._sort_params(**options)
878
881
 
882
+ align_pos = {
883
+ 'right': 1,
884
+ 'left': 0,
885
+ 'top': 1,
886
+ 'bottom': 0,
887
+ 'center': 0.5
888
+ }
889
+
890
+ default_align = {
891
+ "right": "left",
892
+ "left": "right",
893
+ "top": "bottom",
894
+ "bottom": "top"
895
+ }
896
+
879
897
  default_rotation = {
880
898
  "right": -90,
881
899
  "left": 90,
@@ -883,9 +901,31 @@ class _ChunkBase(_LabelBase):
883
901
  "bottom": 0,
884
902
  }
885
903
 
904
+ def get_alignment(self, ha, va, rotation):
905
+ if rotation in {90, -90}:
906
+ ha, va = va, ha # swap the alignment
907
+ align_x, align_y = self.align_pos[ha], self.align_pos[va]
908
+ return align_x, align_y
909
+
910
+ def _align_compact(self, align):
911
+ """Make align keyword compatible to any side"""
912
+ if self.is_flank:
913
+ checker = {"top": "right", "bottom": "left"}
914
+ else:
915
+ checker = {"right": "top", "left": "bottom"}
916
+ return checker.get(align, align)
917
+
886
918
  def get_text_params(self) -> TextParams:
887
- p = TextParams(va="center", ha="center",
888
- rotation=self.default_rotation[self.side])
919
+ if self.align is None:
920
+ self.align = self.default_align[self.side]
921
+
922
+ self.align = self._align_compact(self.align)
923
+ va, ha = self.align, 'center'
924
+ if self.is_flank:
925
+ va, ha = ha, va
926
+
927
+ rotation = self.default_rotation[self.side]
928
+ p = TextParams(rotation=rotation, va=va, ha=ha)
889
929
  p.update_params(self._user_params)
890
930
  return p
891
931
 
@@ -893,6 +933,23 @@ class _ChunkBase(_LabelBase):
893
933
  borderwidth, borderstyle, props):
894
934
 
895
935
  params = self.get_text_params()
936
+ if self.texts_size is not None:
937
+ padding_px = self.padding / 72
938
+ offset_ratio = padding_px / (self.texts_size + padding_px)
939
+ else:
940
+ offset_ratio = 0
941
+
942
+ if self.align == "center":
943
+ const = .5
944
+ elif self.align in ["right", "top"]:
945
+ const = 1 - offset_ratio / 2
946
+ else:
947
+ const = offset_ratio / 2 # self.text_pad / (1 + self.text_pad) / 2
948
+
949
+ # adjust the text alignment based on the alignment position and rotation
950
+ c = .5
951
+ x, y = (const, c) if self.is_flank else (c, const)
952
+
896
953
  fill_colors = [] if fill_colors is None else fill_colors
897
954
  border_colors = [] if border_colors is None else border_colors
898
955
  borderwidth = [] if borderwidth is None else borderwidth
@@ -915,7 +972,8 @@ class _ChunkBase(_LabelBase):
915
972
 
916
973
  if prop is not None:
917
974
  fontdict.update(prop)
918
- ax.text(0.5, 0.5, t, fontdict=fontdict, transform=ax.transAxes)
975
+
976
+ ax.text(x, y, t, fontdict=fontdict, transform=ax.transAxes)
919
977
 
920
978
 
921
979
  class Chunk(_ChunkBase):
@@ -968,12 +1026,19 @@ class Chunk(_ChunkBase):
968
1026
 
969
1027
  def __init__(self, texts,
970
1028
  fill_colors=None,
1029
+ *,
1030
+ align=None,
971
1031
  props=None, padding=8, bordercolor=None,
972
1032
  borderwidth=None, borderstyle=None,
973
1033
  **options):
974
1034
 
975
- super().__init__(texts, fill_colors, props, padding, bordercolor,
976
- borderwidth, borderstyle, **options)
1035
+ super().__init__(texts, fill_colors=fill_colors,
1036
+ align=align,
1037
+ props=props, padding=padding,
1038
+ bordercolor=bordercolor,
1039
+ borderwidth=borderwidth,
1040
+ borderstyle=borderstyle,
1041
+ **options)
977
1042
 
978
1043
  def render(self, axes):
979
1044
 
@@ -1057,11 +1122,12 @@ class FixedChunk(_ChunkBase):
1057
1122
 
1058
1123
  """
1059
1124
 
1060
- def __init__(self, texts, fill_colors=None, ratio=None,
1125
+ def __init__(self, texts, fill_colors=None, *,
1126
+ align=None, ratio=None,
1061
1127
  props=None, padding=8, bordercolor=None,
1062
1128
  borderwidth=None, borderstyle=None,
1063
1129
  **options):
1064
- super().__init__(texts, fill_colors, props, padding, bordercolor,
1130
+ super().__init__(texts, align, fill_colors, props, padding, bordercolor,
1065
1131
  borderwidth, borderstyle, **options)
1066
1132
  if ratio is not None:
1067
1133
  self.set_split_regroup(ratio)
@@ -9,5 +9,4 @@ python:
9
9
  install:
10
10
  - requirements: docs/requirements.txt
11
11
  - method: pip
12
- path: .
13
- system_packages: true
12
+ path: .
File without changes
File without changes
File without changes
File without changes
File without changes