Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- .gitignore +12 -0
- README.md +276 -12
- __init__.py +0 -0
- app.py +16 -0
- css.css +157 -0
- requirements.txt +1 -0
- space.py +149 -0
- src/.gitignore +12 -0
- src/README.md +276 -0
- src/backend/gradio_msaplot/__init__.py +4 -0
- src/backend/gradio_msaplot/msa.py +339 -0
- src/backend/gradio_msaplot/msaplot.py +201 -0
- src/demo/__init__.py +0 -0
- src/demo/app.py +16 -0
- src/demo/css.css +157 -0
- src/demo/requirements.txt +1 -0
- src/demo/space.py +149 -0
- src/frontend/Index.svelte +72 -0
- src/frontend/gradio.config.js +9 -0
- src/frontend/package-lock.json +0 -0
- src/frontend/package.json +47 -0
- src/frontend/shared/Plot.svelte +62 -0
- src/frontend/shared/plot_types/AltairPlot.svelte +157 -0
- src/frontend/shared/plot_types/BokehPlot.svelte +94 -0
- src/frontend/shared/plot_types/MatplotlibPlot.svelte +28 -0
- src/frontend/shared/plot_types/PlotlyPlot.svelte +50 -0
- src/frontend/shared/plot_types/altair_utils.ts +111 -0
- src/frontend/testplot.js +0 -0
- src/pyproject.toml +55 -0
.gitignore
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.eggs/
|
2 |
+
dist/
|
3 |
+
*.pyc
|
4 |
+
__pycache__/
|
5 |
+
*.py[cod]
|
6 |
+
*$py.class
|
7 |
+
__tmp/*
|
8 |
+
*.pyi
|
9 |
+
.mypycache
|
10 |
+
.ruff_cache
|
11 |
+
node_modules
|
12 |
+
backend/**/templates/
|
README.md
CHANGED
@@ -1,12 +1,276 @@
|
|
1 |
-
---
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
---
|
11 |
-
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
tags: [gradio-custom-component, Plot, med, medicine, bio, biology, chem, chemistry, MSA, multiple sequence alignment, seqlogo, annotation, consensus histogram, visualize]
|
3 |
+
title: gradio_msaplot
|
4 |
+
short_description: MSAplot is customizable panels for plotting MSA.
|
5 |
+
colorFrom: blue
|
6 |
+
colorTo: yellow
|
7 |
+
sdk: gradio
|
8 |
+
pinned: false
|
9 |
+
app_file: demo/space.py
|
10 |
+
---
|
11 |
+
|
12 |
+
# `gradio_msaplot`
|
13 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange"> <a href="https://github.com/Josephrp/GradioMSAPlot.git/issues" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/Issues-white?logo=github&logoColor=black"></a> <a href="https://huggingface.co/spaces/seq-to-pheno/MSAPlot/discussions" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/%F0%9F%A4%97%20Discuss-%23097EFF?style=flat&logoColor=black"></a>
|
14 |
+
|
15 |
+
MSAplot is customizable panels for plotting MSA, seqlogo, annotation, and consensus histograms.
|
16 |
+
|
17 |
+
## Installation
|
18 |
+
|
19 |
+
```bash
|
20 |
+
pip install gradio_msaplot
|
21 |
+
```
|
22 |
+
|
23 |
+
## Usage
|
24 |
+
|
25 |
+
```python
|
26 |
+
|
27 |
+
import gradio as gr
|
28 |
+
from gradio_msaplot import MSAPlot, MSAPlotData
|
29 |
+
import matplotlib
|
30 |
+
matplotlib.use('Agg')
|
31 |
+
|
32 |
+
example = MSAPlot().example_value()
|
33 |
+
|
34 |
+
with gr.Blocks() as demo:
|
35 |
+
with gr.Row():
|
36 |
+
MSAPlot(label="Blank"), # blank component
|
37 |
+
MSAPlot(value=example, label="Populated"), # populated component
|
38 |
+
|
39 |
+
|
40 |
+
if __name__ == "__main__":
|
41 |
+
demo.launch()
|
42 |
+
|
43 |
+
```
|
44 |
+
|
45 |
+
## `MSAPlot`
|
46 |
+
|
47 |
+
### Initialization
|
48 |
+
|
49 |
+
<table>
|
50 |
+
<thead>
|
51 |
+
<tr>
|
52 |
+
<th align="left">name</th>
|
53 |
+
<th align="left" style="width: 25%;">type</th>
|
54 |
+
<th align="left">default</th>
|
55 |
+
<th align="left">description</th>
|
56 |
+
</tr>
|
57 |
+
</thead>
|
58 |
+
<tbody>
|
59 |
+
<tr>
|
60 |
+
<td align="left"><code>value</code></td>
|
61 |
+
<td align="left" style="width: 25%;">
|
62 |
+
|
63 |
+
```python
|
64 |
+
typing.Any | None
|
65 |
+
```
|
66 |
+
|
67 |
+
</td>
|
68 |
+
<td align="left"><code>None</code></td>
|
69 |
+
<td align="left">None</td>
|
70 |
+
</tr>
|
71 |
+
|
72 |
+
<tr>
|
73 |
+
<td align="left"><code>label</code></td>
|
74 |
+
<td align="left" style="width: 25%;">
|
75 |
+
|
76 |
+
```python
|
77 |
+
str | None
|
78 |
+
```
|
79 |
+
|
80 |
+
</td>
|
81 |
+
<td align="left"><code>None</code></td>
|
82 |
+
<td align="left">None</td>
|
83 |
+
</tr>
|
84 |
+
|
85 |
+
<tr>
|
86 |
+
<td align="left"><code>every</code></td>
|
87 |
+
<td align="left" style="width: 25%;">
|
88 |
+
|
89 |
+
```python
|
90 |
+
float | None
|
91 |
+
```
|
92 |
+
|
93 |
+
</td>
|
94 |
+
<td align="left"><code>None</code></td>
|
95 |
+
<td align="left">None</td>
|
96 |
+
</tr>
|
97 |
+
|
98 |
+
<tr>
|
99 |
+
<td align="left"><code>show_label</code></td>
|
100 |
+
<td align="left" style="width: 25%;">
|
101 |
+
|
102 |
+
```python
|
103 |
+
bool | None
|
104 |
+
```
|
105 |
+
|
106 |
+
</td>
|
107 |
+
<td align="left"><code>None</code></td>
|
108 |
+
<td align="left">None</td>
|
109 |
+
</tr>
|
110 |
+
|
111 |
+
<tr>
|
112 |
+
<td align="left"><code>container</code></td>
|
113 |
+
<td align="left" style="width: 25%;">
|
114 |
+
|
115 |
+
```python
|
116 |
+
bool
|
117 |
+
```
|
118 |
+
|
119 |
+
</td>
|
120 |
+
<td align="left"><code>True</code></td>
|
121 |
+
<td align="left">None</td>
|
122 |
+
</tr>
|
123 |
+
|
124 |
+
<tr>
|
125 |
+
<td align="left"><code>scale</code></td>
|
126 |
+
<td align="left" style="width: 25%;">
|
127 |
+
|
128 |
+
```python
|
129 |
+
int | None
|
130 |
+
```
|
131 |
+
|
132 |
+
</td>
|
133 |
+
<td align="left"><code>None</code></td>
|
134 |
+
<td align="left">None</td>
|
135 |
+
</tr>
|
136 |
+
|
137 |
+
<tr>
|
138 |
+
<td align="left"><code>min_width</code></td>
|
139 |
+
<td align="left" style="width: 25%;">
|
140 |
+
|
141 |
+
```python
|
142 |
+
int
|
143 |
+
```
|
144 |
+
|
145 |
+
</td>
|
146 |
+
<td align="left"><code>160</code></td>
|
147 |
+
<td align="left">None</td>
|
148 |
+
</tr>
|
149 |
+
|
150 |
+
<tr>
|
151 |
+
<td align="left"><code>visible</code></td>
|
152 |
+
<td align="left" style="width: 25%;">
|
153 |
+
|
154 |
+
```python
|
155 |
+
bool
|
156 |
+
```
|
157 |
+
|
158 |
+
</td>
|
159 |
+
<td align="left"><code>True</code></td>
|
160 |
+
<td align="left">None</td>
|
161 |
+
</tr>
|
162 |
+
|
163 |
+
<tr>
|
164 |
+
<td align="left"><code>elem_id</code></td>
|
165 |
+
<td align="left" style="width: 25%;">
|
166 |
+
|
167 |
+
```python
|
168 |
+
str | None
|
169 |
+
```
|
170 |
+
|
171 |
+
</td>
|
172 |
+
<td align="left"><code>None</code></td>
|
173 |
+
<td align="left">None</td>
|
174 |
+
</tr>
|
175 |
+
|
176 |
+
<tr>
|
177 |
+
<td align="left"><code>elem_classes</code></td>
|
178 |
+
<td align="left" style="width: 25%;">
|
179 |
+
|
180 |
+
```python
|
181 |
+
list[str] | str | None
|
182 |
+
```
|
183 |
+
|
184 |
+
</td>
|
185 |
+
<td align="left"><code>None</code></td>
|
186 |
+
<td align="left">None</td>
|
187 |
+
</tr>
|
188 |
+
|
189 |
+
<tr>
|
190 |
+
<td align="left"><code>render</code></td>
|
191 |
+
<td align="left" style="width: 25%;">
|
192 |
+
|
193 |
+
```python
|
194 |
+
bool
|
195 |
+
```
|
196 |
+
|
197 |
+
</td>
|
198 |
+
<td align="left"><code>True</code></td>
|
199 |
+
<td align="left">None</td>
|
200 |
+
</tr>
|
201 |
+
|
202 |
+
<tr>
|
203 |
+
<td align="left"><code>key</code></td>
|
204 |
+
<td align="left" style="width: 25%;">
|
205 |
+
|
206 |
+
```python
|
207 |
+
int | str | None
|
208 |
+
```
|
209 |
+
|
210 |
+
</td>
|
211 |
+
<td align="left"><code>None</code></td>
|
212 |
+
<td align="left">None</td>
|
213 |
+
</tr>
|
214 |
+
</tbody></table>
|
215 |
+
|
216 |
+
|
217 |
+
### Events
|
218 |
+
|
219 |
+
| name | description |
|
220 |
+
|:-----|:------------|
|
221 |
+
| `change` | Triggered when the value of the MSAPlot changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input. |
|
222 |
+
| `clear` | Triggered when the plot is cleared. |
|
223 |
+
|
224 |
+
|
225 |
+
|
226 |
+
### User function
|
227 |
+
|
228 |
+
The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
|
229 |
+
|
230 |
+
- When used as an Input, the component only impacts the input signature of the user function.
|
231 |
+
- When used as an output, the component only impacts the return signature of the user function.
|
232 |
+
|
233 |
+
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
234 |
+
|
235 |
+
- **As output:** Is passed, the preprocessed input data sent to the user's function in the backend.
|
236 |
+
- **As input:** Should return, the output data received by the component from the user's function in the backend.
|
237 |
+
|
238 |
+
```python
|
239 |
+
def predict(
|
240 |
+
value: MSAPlotData | None
|
241 |
+
) -> MSAPlotData:
|
242 |
+
return value
|
243 |
+
```
|
244 |
+
|
245 |
+
|
246 |
+
## `MSAPlotData`
|
247 |
+
|
248 |
+
### Initialization
|
249 |
+
|
250 |
+
<table>
|
251 |
+
<thead>
|
252 |
+
<tr>
|
253 |
+
<th align="left">name</th>
|
254 |
+
<th align="left" style="width: 25%;">type</th>
|
255 |
+
<th align="left">default</th>
|
256 |
+
<th align="left">description</th>
|
257 |
+
</tr>
|
258 |
+
</thead>
|
259 |
+
<tbody>
|
260 |
+
<tr>
|
261 |
+
<td align="left"><code>data</code></td>
|
262 |
+
<td align="left" style="width: 25%;">
|
263 |
+
|
264 |
+
```python
|
265 |
+
typing.Any
|
266 |
+
```
|
267 |
+
|
268 |
+
</td>
|
269 |
+
<td align="left"><code>None</code></td>
|
270 |
+
<td align="left">None</td>
|
271 |
+
</tr>
|
272 |
+
</tbody></table>
|
273 |
+
|
274 |
+
|
275 |
+
|
276 |
+
|
__init__.py
ADDED
File without changes
|
app.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import gradio as gr
|
3 |
+
from gradio_msaplot import MSAPlot, MSAPlotData
|
4 |
+
import matplotlib
|
5 |
+
matplotlib.use('Agg')
|
6 |
+
|
7 |
+
example = MSAPlot().example_value()
|
8 |
+
|
9 |
+
with gr.Blocks() as demo:
|
10 |
+
with gr.Row():
|
11 |
+
MSAPlot(label="Blank"), # blank component
|
12 |
+
MSAPlot(value=example, label="Populated"), # populated component
|
13 |
+
|
14 |
+
|
15 |
+
if __name__ == "__main__":
|
16 |
+
demo.launch()
|
css.css
ADDED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
html {
|
2 |
+
font-family: Inter;
|
3 |
+
font-size: 16px;
|
4 |
+
font-weight: 400;
|
5 |
+
line-height: 1.5;
|
6 |
+
-webkit-text-size-adjust: 100%;
|
7 |
+
background: #fff;
|
8 |
+
color: #323232;
|
9 |
+
-webkit-font-smoothing: antialiased;
|
10 |
+
-moz-osx-font-smoothing: grayscale;
|
11 |
+
text-rendering: optimizeLegibility;
|
12 |
+
}
|
13 |
+
|
14 |
+
:root {
|
15 |
+
--space: 1;
|
16 |
+
--vspace: calc(var(--space) * 1rem);
|
17 |
+
--vspace-0: calc(3 * var(--space) * 1rem);
|
18 |
+
--vspace-1: calc(2 * var(--space) * 1rem);
|
19 |
+
--vspace-2: calc(1.5 * var(--space) * 1rem);
|
20 |
+
--vspace-3: calc(0.5 * var(--space) * 1rem);
|
21 |
+
}
|
22 |
+
|
23 |
+
.app {
|
24 |
+
max-width: 748px !important;
|
25 |
+
}
|
26 |
+
|
27 |
+
.prose p {
|
28 |
+
margin: var(--vspace) 0;
|
29 |
+
line-height: var(--vspace * 2);
|
30 |
+
font-size: 1rem;
|
31 |
+
}
|
32 |
+
|
33 |
+
code {
|
34 |
+
font-family: "Inconsolata", sans-serif;
|
35 |
+
font-size: 16px;
|
36 |
+
}
|
37 |
+
|
38 |
+
h1,
|
39 |
+
h1 code {
|
40 |
+
font-weight: 400;
|
41 |
+
line-height: calc(2.5 / var(--space) * var(--vspace));
|
42 |
+
}
|
43 |
+
|
44 |
+
h1 code {
|
45 |
+
background: none;
|
46 |
+
border: none;
|
47 |
+
letter-spacing: 0.05em;
|
48 |
+
padding-bottom: 5px;
|
49 |
+
position: relative;
|
50 |
+
padding: 0;
|
51 |
+
}
|
52 |
+
|
53 |
+
h2 {
|
54 |
+
margin: var(--vspace-1) 0 var(--vspace-2) 0;
|
55 |
+
line-height: 1em;
|
56 |
+
}
|
57 |
+
|
58 |
+
h3,
|
59 |
+
h3 code {
|
60 |
+
margin: var(--vspace-1) 0 var(--vspace-2) 0;
|
61 |
+
line-height: 1em;
|
62 |
+
}
|
63 |
+
|
64 |
+
h4,
|
65 |
+
h5,
|
66 |
+
h6 {
|
67 |
+
margin: var(--vspace-3) 0 var(--vspace-3) 0;
|
68 |
+
line-height: var(--vspace);
|
69 |
+
}
|
70 |
+
|
71 |
+
.bigtitle,
|
72 |
+
h1,
|
73 |
+
h1 code {
|
74 |
+
font-size: calc(8px * 4.5);
|
75 |
+
word-break: break-word;
|
76 |
+
}
|
77 |
+
|
78 |
+
.title,
|
79 |
+
h2,
|
80 |
+
h2 code {
|
81 |
+
font-size: calc(8px * 3.375);
|
82 |
+
font-weight: lighter;
|
83 |
+
word-break: break-word;
|
84 |
+
border: none;
|
85 |
+
background: none;
|
86 |
+
}
|
87 |
+
|
88 |
+
.subheading1,
|
89 |
+
h3,
|
90 |
+
h3 code {
|
91 |
+
font-size: calc(8px * 1.8);
|
92 |
+
font-weight: 600;
|
93 |
+
border: none;
|
94 |
+
background: none;
|
95 |
+
letter-spacing: 0.1em;
|
96 |
+
text-transform: uppercase;
|
97 |
+
}
|
98 |
+
|
99 |
+
h2 code {
|
100 |
+
padding: 0;
|
101 |
+
position: relative;
|
102 |
+
letter-spacing: 0.05em;
|
103 |
+
}
|
104 |
+
|
105 |
+
blockquote {
|
106 |
+
font-size: calc(8px * 1.1667);
|
107 |
+
font-style: italic;
|
108 |
+
line-height: calc(1.1667 * var(--vspace));
|
109 |
+
margin: var(--vspace-2) var(--vspace-2);
|
110 |
+
}
|
111 |
+
|
112 |
+
.subheading2,
|
113 |
+
h4 {
|
114 |
+
font-size: calc(8px * 1.4292);
|
115 |
+
text-transform: uppercase;
|
116 |
+
font-weight: 600;
|
117 |
+
}
|
118 |
+
|
119 |
+
.subheading3,
|
120 |
+
h5 {
|
121 |
+
font-size: calc(8px * 1.2917);
|
122 |
+
line-height: calc(1.2917 * var(--vspace));
|
123 |
+
|
124 |
+
font-weight: lighter;
|
125 |
+
text-transform: uppercase;
|
126 |
+
letter-spacing: 0.15em;
|
127 |
+
}
|
128 |
+
|
129 |
+
h6 {
|
130 |
+
font-size: calc(8px * 1.1667);
|
131 |
+
font-size: 1.1667em;
|
132 |
+
font-weight: normal;
|
133 |
+
font-style: italic;
|
134 |
+
font-family: "le-monde-livre-classic-byol", serif !important;
|
135 |
+
letter-spacing: 0px !important;
|
136 |
+
}
|
137 |
+
|
138 |
+
#start .md > *:first-child {
|
139 |
+
margin-top: 0;
|
140 |
+
}
|
141 |
+
|
142 |
+
h2 + h3 {
|
143 |
+
margin-top: 0;
|
144 |
+
}
|
145 |
+
|
146 |
+
.md hr {
|
147 |
+
border: none;
|
148 |
+
border-top: 1px solid var(--block-border-color);
|
149 |
+
margin: var(--vspace-2) 0 var(--vspace-2) 0;
|
150 |
+
}
|
151 |
+
.prose ul {
|
152 |
+
margin: var(--vspace-2) 0 var(--vspace-1) 0;
|
153 |
+
}
|
154 |
+
|
155 |
+
.gap {
|
156 |
+
gap: 0;
|
157 |
+
}
|
requirements.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
gradio_msaplot
|
space.py
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import gradio as gr
|
3 |
+
from app import demo as app
|
4 |
+
import os
|
5 |
+
|
6 |
+
_docs = {'MSAPlot': {'description': 'Creates a Multiple Sequence Alignment (MSA) plot component.', 'members': {'__init__': {'value': {'type': 'typing.Any | None', 'default': 'None', 'description': None}, 'label': {'type': 'str | None', 'default': 'None', 'description': None}, 'every': {'type': 'float | None', 'default': 'None', 'description': None}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': None}, 'container': {'type': 'bool', 'default': 'True', 'description': None}, 'scale': {'type': 'int | None', 'default': 'None', 'description': None}, 'min_width': {'type': 'int', 'default': '160', 'description': None}, 'visible': {'type': 'bool', 'default': 'True', 'description': None}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': None}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': None}, 'render': {'type': 'bool', 'default': 'True', 'description': None}, 'key': {'type': 'int | str | None', 'default': 'None', 'description': None}}, 'postprocess': {'value': {'type': 'MSAPlotData', 'description': "The output data received by the component from the user's function in the backend."}}, 'preprocess': {'return': {'type': 'MSAPlotData | None', 'description': "The preprocessed input data sent to the user's function in the backend."}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the MSAPlot changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'clear': {'type': None, 'default': None, 'description': 'Triggered when the plot is cleared.'}}}, '__meta__': {'additional_interfaces': {}}, 'MSAPlotData': {'description': 'Helper class that provides a standard way to create an ABC using\ninheritance.', 'members': {'__init__': {'data': {'type': 'typing.Any', 'default': 'None', 'description': None}}}, 'events': {}}}
|
7 |
+
|
8 |
+
abs_path = os.path.join(os.path.dirname(__file__), "css.css")
|
9 |
+
|
10 |
+
with gr.Blocks(
|
11 |
+
css=abs_path,
|
12 |
+
theme=gr.themes.Default(
|
13 |
+
font_mono=[
|
14 |
+
gr.themes.GoogleFont("Inconsolata"),
|
15 |
+
"monospace",
|
16 |
+
],
|
17 |
+
),
|
18 |
+
) as demo:
|
19 |
+
gr.Markdown(
|
20 |
+
"""
|
21 |
+
# `gradio_msaplot`
|
22 |
+
|
23 |
+
<div style="display: flex; gap: 7px;">
|
24 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange"> <a href="https://github.com/Josephrp/GradioMSAPlot.git/issues" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/Issues-white?logo=github&logoColor=black"></a> <a href="https://huggingface.co/spaces/seq-to-pheno/MSAPlot/discussions" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/%F0%9F%A4%97%20Discuss-%23097EFF?style=flat&logoColor=black"></a>
|
25 |
+
</div>
|
26 |
+
|
27 |
+
MSAplot is customizable panels for plotting MSA, seqlogo, annotation, and consensus histograms.
|
28 |
+
""", elem_classes=["md-custom"], header_links=True)
|
29 |
+
app.render()
|
30 |
+
gr.Markdown(
|
31 |
+
"""
|
32 |
+
## Installation
|
33 |
+
|
34 |
+
```bash
|
35 |
+
pip install gradio_msaplot
|
36 |
+
```
|
37 |
+
|
38 |
+
## Usage
|
39 |
+
|
40 |
+
```python
|
41 |
+
|
42 |
+
import gradio as gr
|
43 |
+
from gradio_msaplot import MSAPlot, MSAPlotData
|
44 |
+
import matplotlib
|
45 |
+
matplotlib.use('Agg')
|
46 |
+
|
47 |
+
example = MSAPlot().example_value()
|
48 |
+
|
49 |
+
with gr.Blocks() as demo:
|
50 |
+
with gr.Row():
|
51 |
+
MSAPlot(label="Blank"), # blank component
|
52 |
+
MSAPlot(value=example, label="Populated"), # populated component
|
53 |
+
|
54 |
+
|
55 |
+
if __name__ == "__main__":
|
56 |
+
demo.launch()
|
57 |
+
|
58 |
+
```
|
59 |
+
""", elem_classes=["md-custom"], header_links=True)
|
60 |
+
|
61 |
+
|
62 |
+
gr.Markdown("""
|
63 |
+
## `MSAPlot`
|
64 |
+
|
65 |
+
### Initialization
|
66 |
+
""", elem_classes=["md-custom"], header_links=True)
|
67 |
+
|
68 |
+
gr.ParamViewer(value=_docs["MSAPlot"]["members"]["__init__"], linkify=[])
|
69 |
+
|
70 |
+
|
71 |
+
gr.Markdown("### Events")
|
72 |
+
gr.ParamViewer(value=_docs["MSAPlot"]["events"], linkify=['Event'])
|
73 |
+
|
74 |
+
|
75 |
+
|
76 |
+
|
77 |
+
gr.Markdown("""
|
78 |
+
|
79 |
+
### User function
|
80 |
+
|
81 |
+
The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
|
82 |
+
|
83 |
+
- When used as an Input, the component only impacts the input signature of the user function.
|
84 |
+
- When used as an output, the component only impacts the return signature of the user function.
|
85 |
+
|
86 |
+
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
87 |
+
|
88 |
+
- **As input:** Is passed, the preprocessed input data sent to the user's function in the backend.
|
89 |
+
- **As output:** Should return, the output data received by the component from the user's function in the backend.
|
90 |
+
|
91 |
+
```python
|
92 |
+
def predict(
|
93 |
+
value: MSAPlotData | None
|
94 |
+
) -> MSAPlotData:
|
95 |
+
return value
|
96 |
+
```
|
97 |
+
""", elem_classes=["md-custom", "MSAPlot-user-fn"], header_links=True)
|
98 |
+
|
99 |
+
|
100 |
+
gr.Markdown("""
|
101 |
+
## `MSAPlotData`
|
102 |
+
|
103 |
+
### Initialization
|
104 |
+
""", elem_classes=["md-custom"], header_links=True)
|
105 |
+
|
106 |
+
gr.ParamViewer(value=_docs["MSAPlotData"]["members"]["__init__"], linkify=[])
|
107 |
+
|
108 |
+
|
109 |
+
|
110 |
+
|
111 |
+
|
112 |
+
|
113 |
+
|
114 |
+
demo.load(None, js=r"""function() {
|
115 |
+
const refs = {};
|
116 |
+
const user_fn_refs = {};
|
117 |
+
requestAnimationFrame(() => {
|
118 |
+
|
119 |
+
Object.entries(user_fn_refs).forEach(([key, refs]) => {
|
120 |
+
if (refs.length > 0) {
|
121 |
+
const el = document.querySelector(`.${key}-user-fn`);
|
122 |
+
if (!el) return;
|
123 |
+
refs.forEach(ref => {
|
124 |
+
el.innerHTML = el.innerHTML.replace(
|
125 |
+
new RegExp("\\b"+ref+"\\b", "g"),
|
126 |
+
`<a href="#h-${ref.toLowerCase()}">${ref}</a>`
|
127 |
+
);
|
128 |
+
})
|
129 |
+
}
|
130 |
+
})
|
131 |
+
|
132 |
+
Object.entries(refs).forEach(([key, refs]) => {
|
133 |
+
if (refs.length > 0) {
|
134 |
+
const el = document.querySelector(`.${key}`);
|
135 |
+
if (!el) return;
|
136 |
+
refs.forEach(ref => {
|
137 |
+
el.innerHTML = el.innerHTML.replace(
|
138 |
+
new RegExp("\\b"+ref+"\\b", "g"),
|
139 |
+
`<a href="#h-${ref.toLowerCase()}">${ref}</a>`
|
140 |
+
);
|
141 |
+
})
|
142 |
+
}
|
143 |
+
})
|
144 |
+
})
|
145 |
+
}
|
146 |
+
|
147 |
+
""")
|
148 |
+
|
149 |
+
demo.launch()
|
src/.gitignore
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.eggs/
|
2 |
+
dist/
|
3 |
+
*.pyc
|
4 |
+
__pycache__/
|
5 |
+
*.py[cod]
|
6 |
+
*$py.class
|
7 |
+
__tmp/*
|
8 |
+
*.pyi
|
9 |
+
.mypycache
|
10 |
+
.ruff_cache
|
11 |
+
node_modules
|
12 |
+
backend/**/templates/
|
src/README.md
ADDED
@@ -0,0 +1,276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
tags: [gradio-custom-component, Plot, med, medicine, bio, biology, chem, chemistry, MSA, multiple sequence alignment, seqlogo, annotation, consensus histogram, visualize]
|
3 |
+
title: gradio_msaplot
|
4 |
+
short_description: MSAplot is customizable panels for plotting MSA.
|
5 |
+
colorFrom: blue
|
6 |
+
colorTo: yellow
|
7 |
+
sdk: gradio
|
8 |
+
pinned: false
|
9 |
+
app_file: demo/space.py
|
10 |
+
---
|
11 |
+
|
12 |
+
# `gradio_msaplot`
|
13 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange"> <a href="https://github.com/Josephrp/GradioMSAPlot.git/issues" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/Issues-white?logo=github&logoColor=black"></a> <a href="https://huggingface.co/spaces/seq-to-pheno/MSAPlot/discussions" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/%F0%9F%A4%97%20Discuss-%23097EFF?style=flat&logoColor=black"></a>
|
14 |
+
|
15 |
+
MSAplot is customizable panels for plotting MSA, seqlogo, annotation, and consensus histograms.
|
16 |
+
|
17 |
+
## Installation
|
18 |
+
|
19 |
+
```bash
|
20 |
+
pip install gradio_msaplot
|
21 |
+
```
|
22 |
+
|
23 |
+
## Usage
|
24 |
+
|
25 |
+
```python
|
26 |
+
|
27 |
+
import gradio as gr
|
28 |
+
from gradio_msaplot import MSAPlot, MSAPlotData
|
29 |
+
import matplotlib
|
30 |
+
matplotlib.use('Agg')
|
31 |
+
|
32 |
+
example = MSAPlot().example_value()
|
33 |
+
|
34 |
+
with gr.Blocks() as demo:
|
35 |
+
with gr.Row():
|
36 |
+
MSAPlot(label="Blank"), # blank component
|
37 |
+
MSAPlot(value=example, label="Populated"), # populated component
|
38 |
+
|
39 |
+
|
40 |
+
if __name__ == "__main__":
|
41 |
+
demo.launch()
|
42 |
+
|
43 |
+
```
|
44 |
+
|
45 |
+
## `MSAPlot`
|
46 |
+
|
47 |
+
### Initialization
|
48 |
+
|
49 |
+
<table>
|
50 |
+
<thead>
|
51 |
+
<tr>
|
52 |
+
<th align="left">name</th>
|
53 |
+
<th align="left" style="width: 25%;">type</th>
|
54 |
+
<th align="left">default</th>
|
55 |
+
<th align="left">description</th>
|
56 |
+
</tr>
|
57 |
+
</thead>
|
58 |
+
<tbody>
|
59 |
+
<tr>
|
60 |
+
<td align="left"><code>value</code></td>
|
61 |
+
<td align="left" style="width: 25%;">
|
62 |
+
|
63 |
+
```python
|
64 |
+
typing.Any | None
|
65 |
+
```
|
66 |
+
|
67 |
+
</td>
|
68 |
+
<td align="left"><code>None</code></td>
|
69 |
+
<td align="left">None</td>
|
70 |
+
</tr>
|
71 |
+
|
72 |
+
<tr>
|
73 |
+
<td align="left"><code>label</code></td>
|
74 |
+
<td align="left" style="width: 25%;">
|
75 |
+
|
76 |
+
```python
|
77 |
+
str | None
|
78 |
+
```
|
79 |
+
|
80 |
+
</td>
|
81 |
+
<td align="left"><code>None</code></td>
|
82 |
+
<td align="left">None</td>
|
83 |
+
</tr>
|
84 |
+
|
85 |
+
<tr>
|
86 |
+
<td align="left"><code>every</code></td>
|
87 |
+
<td align="left" style="width: 25%;">
|
88 |
+
|
89 |
+
```python
|
90 |
+
float | None
|
91 |
+
```
|
92 |
+
|
93 |
+
</td>
|
94 |
+
<td align="left"><code>None</code></td>
|
95 |
+
<td align="left">None</td>
|
96 |
+
</tr>
|
97 |
+
|
98 |
+
<tr>
|
99 |
+
<td align="left"><code>show_label</code></td>
|
100 |
+
<td align="left" style="width: 25%;">
|
101 |
+
|
102 |
+
```python
|
103 |
+
bool | None
|
104 |
+
```
|
105 |
+
|
106 |
+
</td>
|
107 |
+
<td align="left"><code>None</code></td>
|
108 |
+
<td align="left">None</td>
|
109 |
+
</tr>
|
110 |
+
|
111 |
+
<tr>
|
112 |
+
<td align="left"><code>container</code></td>
|
113 |
+
<td align="left" style="width: 25%;">
|
114 |
+
|
115 |
+
```python
|
116 |
+
bool
|
117 |
+
```
|
118 |
+
|
119 |
+
</td>
|
120 |
+
<td align="left"><code>True</code></td>
|
121 |
+
<td align="left">None</td>
|
122 |
+
</tr>
|
123 |
+
|
124 |
+
<tr>
|
125 |
+
<td align="left"><code>scale</code></td>
|
126 |
+
<td align="left" style="width: 25%;">
|
127 |
+
|
128 |
+
```python
|
129 |
+
int | None
|
130 |
+
```
|
131 |
+
|
132 |
+
</td>
|
133 |
+
<td align="left"><code>None</code></td>
|
134 |
+
<td align="left">None</td>
|
135 |
+
</tr>
|
136 |
+
|
137 |
+
<tr>
|
138 |
+
<td align="left"><code>min_width</code></td>
|
139 |
+
<td align="left" style="width: 25%;">
|
140 |
+
|
141 |
+
```python
|
142 |
+
int
|
143 |
+
```
|
144 |
+
|
145 |
+
</td>
|
146 |
+
<td align="left"><code>160</code></td>
|
147 |
+
<td align="left">None</td>
|
148 |
+
</tr>
|
149 |
+
|
150 |
+
<tr>
|
151 |
+
<td align="left"><code>visible</code></td>
|
152 |
+
<td align="left" style="width: 25%;">
|
153 |
+
|
154 |
+
```python
|
155 |
+
bool
|
156 |
+
```
|
157 |
+
|
158 |
+
</td>
|
159 |
+
<td align="left"><code>True</code></td>
|
160 |
+
<td align="left">None</td>
|
161 |
+
</tr>
|
162 |
+
|
163 |
+
<tr>
|
164 |
+
<td align="left"><code>elem_id</code></td>
|
165 |
+
<td align="left" style="width: 25%;">
|
166 |
+
|
167 |
+
```python
|
168 |
+
str | None
|
169 |
+
```
|
170 |
+
|
171 |
+
</td>
|
172 |
+
<td align="left"><code>None</code></td>
|
173 |
+
<td align="left">None</td>
|
174 |
+
</tr>
|
175 |
+
|
176 |
+
<tr>
|
177 |
+
<td align="left"><code>elem_classes</code></td>
|
178 |
+
<td align="left" style="width: 25%;">
|
179 |
+
|
180 |
+
```python
|
181 |
+
list[str] | str | None
|
182 |
+
```
|
183 |
+
|
184 |
+
</td>
|
185 |
+
<td align="left"><code>None</code></td>
|
186 |
+
<td align="left">None</td>
|
187 |
+
</tr>
|
188 |
+
|
189 |
+
<tr>
|
190 |
+
<td align="left"><code>render</code></td>
|
191 |
+
<td align="left" style="width: 25%;">
|
192 |
+
|
193 |
+
```python
|
194 |
+
bool
|
195 |
+
```
|
196 |
+
|
197 |
+
</td>
|
198 |
+
<td align="left"><code>True</code></td>
|
199 |
+
<td align="left">None</td>
|
200 |
+
</tr>
|
201 |
+
|
202 |
+
<tr>
|
203 |
+
<td align="left"><code>key</code></td>
|
204 |
+
<td align="left" style="width: 25%;">
|
205 |
+
|
206 |
+
```python
|
207 |
+
int | str | None
|
208 |
+
```
|
209 |
+
|
210 |
+
</td>
|
211 |
+
<td align="left"><code>None</code></td>
|
212 |
+
<td align="left">None</td>
|
213 |
+
</tr>
|
214 |
+
</tbody></table>
|
215 |
+
|
216 |
+
|
217 |
+
### Events
|
218 |
+
|
219 |
+
| name | description |
|
220 |
+
|:-----|:------------|
|
221 |
+
| `change` | Triggered when the value of the MSAPlot changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input. |
|
222 |
+
| `clear` | Triggered when the plot is cleared. |
|
223 |
+
|
224 |
+
|
225 |
+
|
226 |
+
### User function
|
227 |
+
|
228 |
+
The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
|
229 |
+
|
230 |
+
- When used as an Input, the component only impacts the input signature of the user function.
|
231 |
+
- When used as an output, the component only impacts the return signature of the user function.
|
232 |
+
|
233 |
+
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
234 |
+
|
235 |
+
- **As output:** Is passed, the preprocessed input data sent to the user's function in the backend.
|
236 |
+
- **As input:** Should return, the output data received by the component from the user's function in the backend.
|
237 |
+
|
238 |
+
```python
|
239 |
+
def predict(
|
240 |
+
value: MSAPlotData | None
|
241 |
+
) -> MSAPlotData:
|
242 |
+
return value
|
243 |
+
```
|
244 |
+
|
245 |
+
|
246 |
+
## `MSAPlotData`
|
247 |
+
|
248 |
+
### Initialization
|
249 |
+
|
250 |
+
<table>
|
251 |
+
<thead>
|
252 |
+
<tr>
|
253 |
+
<th align="left">name</th>
|
254 |
+
<th align="left" style="width: 25%;">type</th>
|
255 |
+
<th align="left">default</th>
|
256 |
+
<th align="left">description</th>
|
257 |
+
</tr>
|
258 |
+
</thead>
|
259 |
+
<tbody>
|
260 |
+
<tr>
|
261 |
+
<td align="left"><code>data</code></td>
|
262 |
+
<td align="left" style="width: 25%;">
|
263 |
+
|
264 |
+
```python
|
265 |
+
typing.Any
|
266 |
+
```
|
267 |
+
|
268 |
+
</td>
|
269 |
+
<td align="left"><code>None</code></td>
|
270 |
+
<td align="left">None</td>
|
271 |
+
</tr>
|
272 |
+
</tbody></table>
|
273 |
+
|
274 |
+
|
275 |
+
|
276 |
+
|
src/backend/gradio_msaplot/__init__.py
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
from .msaplot import MSAPlot, MSAPlotData
|
3 |
+
|
4 |
+
__all__ = ["MSAPlot", "MSAPlotData"]
|
src/backend/gradio_msaplot/msa.py
ADDED
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import matplotlib.pyplot as plt
|
3 |
+
import matplotlib.patches as patches
|
4 |
+
import matplotlib.transforms as transforms
|
5 |
+
from matplotlib.transforms import Affine2D
|
6 |
+
import math
|
7 |
+
from matplotlib.patheffects import RendererBase
|
8 |
+
import matplotlib.patheffects as PathEffects
|
9 |
+
import seaborn as sns
|
10 |
+
|
11 |
+
# Calculate the font size that match the unit length on x-axis or y-axis
|
12 |
+
def CalculateFontsize(xlim, ylim, ax, fig, rows, cols, unit_scale=1):
|
13 |
+
# Get axis limits
|
14 |
+
axXlim = ax.get_xlim()
|
15 |
+
axYlim = ax.get_ylim()
|
16 |
+
|
17 |
+
# Get figure dimensions in pixels
|
18 |
+
fig_width, fig_height = fig.get_size_inches() * fig.dpi
|
19 |
+
|
20 |
+
# Get axis dimensions in pixels
|
21 |
+
bbox = ax.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
|
22 |
+
ax_width, ax_height = bbox.width * fig.dpi, bbox.height * fig.dpi
|
23 |
+
|
24 |
+
# Calculate font size proportional to axis units
|
25 |
+
fontsize_x = unit_scale * ax_width / (axXlim[1] - axXlim[0]) / cols * (xlim[1] - xlim[0])
|
26 |
+
fontsize_y = 0.8 * unit_scale * ax_height / (axYlim[1] - axYlim[0]) / rows * (ylim[1] - ylim[0])
|
27 |
+
|
28 |
+
# Use the minimum of the two to keep the font size consistent
|
29 |
+
fontsize = min(fontsize_x, fontsize_y)
|
30 |
+
|
31 |
+
return fontsize
|
32 |
+
|
33 |
+
|
34 |
+
def GetStartEnd(msa, start, end):
|
35 |
+
length = len(msa[0])
|
36 |
+
if (start == None):
|
37 |
+
start = 0
|
38 |
+
elif (start < 0):
|
39 |
+
start = length + start
|
40 |
+
if (end == None):
|
41 |
+
end = length - 1
|
42 |
+
elif (end < 0):
|
43 |
+
end = length + end
|
44 |
+
return start, end
|
45 |
+
|
46 |
+
|
47 |
+
def GetColorMap(preset = None, msa=None, color_order = None, palette = None):
|
48 |
+
color_map = {}
|
49 |
+
if preset in ["dna", "nuc", "nucleotide"]:
|
50 |
+
color_map['A'] = [0,1,0]
|
51 |
+
color_map['C'] = [1,165/255,0]
|
52 |
+
color_map['G'] = [1,0,0]
|
53 |
+
color_map['T'] = [0.5,0.5,1]
|
54 |
+
else:
|
55 |
+
if (color_order != None):
|
56 |
+
for i,c in enumerate(color_order):
|
57 |
+
color_map[c] = sns.color_palette(palette)[i]
|
58 |
+
|
59 |
+
# The case some of the alphabet color is not specified in the order
|
60 |
+
for a in msa:
|
61 |
+
for c in a:
|
62 |
+
if c not in color_map:
|
63 |
+
size = len(color_map)
|
64 |
+
color_map[c] = sns.color_palette(palette)[size]
|
65 |
+
|
66 |
+
color_map['-'] = [1,1,1] # white
|
67 |
+
color_map['.'] = [1,1,1] # white
|
68 |
+
return color_map
|
69 |
+
|
70 |
+
|
71 |
+
def DrawMSA(msa, seq_names = None, start = None, end = None,
|
72 |
+
axlim = None, color_map = None, palette=None, ax=None, fig=None,
|
73 |
+
show_char=True):
|
74 |
+
# Get the canvas attributes.
|
75 |
+
ax = ax or plt.gca()
|
76 |
+
|
77 |
+
fig = fig or ax.get_figure()
|
78 |
+
renderer = fig.canvas.get_renderer()
|
79 |
+
|
80 |
+
height = len(msa)
|
81 |
+
length = len(msa[0])
|
82 |
+
|
83 |
+
# start, end: draw the [start,end] (both inclusive) region of the MSA
|
84 |
+
start, end = GetStartEnd(msa, start, end)
|
85 |
+
|
86 |
+
if (axlim == None):
|
87 |
+
fontsize = CalculateFontsize(ax.get_xlim(), ax.get_ylim(), ax, fig, height, end - start + 1)
|
88 |
+
else:
|
89 |
+
fontsize = CalculateFontsize(axlim[0], axlim[1], ax, fig, height, end - start + 1)
|
90 |
+
|
91 |
+
color_map = color_map or GetColorMap(msa=msa, color_order=None, palette=palette)
|
92 |
+
|
93 |
+
lengthUnit = 1 / (end - start + 1)
|
94 |
+
heightUnit = 1 / height
|
95 |
+
if (axlim != None):
|
96 |
+
lengthUnit = (axlim[0][1] - axlim[0][0]) / (end - start + 1)
|
97 |
+
heightUnit = (axlim[1][1] - axlim[1][0]) / height
|
98 |
+
|
99 |
+
for i, a in enumerate(msa):
|
100 |
+
for j,c in enumerate(a[start:end+1]):
|
101 |
+
linewidth = min(2,fontsize/50)
|
102 |
+
if show_char:
|
103 |
+
text = ax.text(x=(j + 0.5)*lengthUnit, y=(i+0.5) * heightUnit, s=c, color="black",
|
104 |
+
va="center_baseline", ha="center", fontsize=fontsize,
|
105 |
+
transform=ax.transAxes if axlim == None else ax.transData)
|
106 |
+
text.set_path_effects([PathEffects.withStroke(linewidth=linewidth,
|
107 |
+
foreground='w')])
|
108 |
+
ax.add_patch( patches.Rectangle(xy=(j * lengthUnit, i * heightUnit),
|
109 |
+
width = lengthUnit, height=heightUnit,
|
110 |
+
facecolor=color_map[c], linewidth=linewidth, edgecolor="white",
|
111 |
+
transform=ax.transAxes if axlim == None else ax.transData))
|
112 |
+
if (axlim == None):
|
113 |
+
ax.set_xlim(-0.5, end - start + 1 - 0.5)
|
114 |
+
ax.set_ylim(0-0.5, height-0.5)
|
115 |
+
|
116 |
+
# Set the x ticks adaptively at point easy to count
|
117 |
+
ticks = []
|
118 |
+
tickLabels = []
|
119 |
+
|
120 |
+
candidateSteps = [1,5,10,20,50,100,500,1000,5000,10000]
|
121 |
+
step = 1
|
122 |
+
for i,s in enumerate(candidateSteps):
|
123 |
+
if (s * 5 > end - start + 1):
|
124 |
+
if (i > 0):
|
125 |
+
step = candidateSteps[i - 1]
|
126 |
+
break
|
127 |
+
|
128 |
+
tickStart = (int)(start / step) * step + step - 1
|
129 |
+
if (tickStart != start):
|
130 |
+
ticks.append(0)
|
131 |
+
tickLabels.append(start+1)
|
132 |
+
for i in range(tickStart, end + 1, step):
|
133 |
+
if (i >= length):
|
134 |
+
break
|
135 |
+
ticks.append(i - start)
|
136 |
+
tickLabels.append(i+1)
|
137 |
+
if (tickLabels[-1] != min(length, end + 1)):
|
138 |
+
ticks.append(min(length - 1, end) - start)
|
139 |
+
tickLabels.append(min(length, end+1))
|
140 |
+
ax.set_xticks(ticks, tickLabels)
|
141 |
+
|
142 |
+
# Set the y
|
143 |
+
ticks = []
|
144 |
+
tickLabels = []
|
145 |
+
for i in range(height):
|
146 |
+
ticks.append(i)
|
147 |
+
tickLabels.append(i if seq_names == None else seq_names[i])
|
148 |
+
ax.set_yticks(ticks, tickLabels)
|
149 |
+
ax.spines['top'].set_visible(False)
|
150 |
+
ax.spines['right'].set_visible(False)
|
151 |
+
ax.spines['left'].set_visible(False)
|
152 |
+
ax.spines['bottom'].set_visible(False)
|
153 |
+
return ax, color_map
|
154 |
+
|
155 |
+
def GetConsensus(msa, start = None, end = None):
|
156 |
+
start, end = GetStartEnd(msa, start, end)
|
157 |
+
consensus = []
|
158 |
+
consensusComposition = []
|
159 |
+
for j in range(start, end + 1):
|
160 |
+
composition = {}
|
161 |
+
if (j >= len(msa[0])):
|
162 |
+
consensus.append('')
|
163 |
+
consensusComposition.append({"":0})
|
164 |
+
continue
|
165 |
+
|
166 |
+
for i,a in enumerate(msa):
|
167 |
+
if (a[j] not in composition):
|
168 |
+
composition[a[j]] = 0
|
169 |
+
composition[a[j]] += 1
|
170 |
+
result = ""
|
171 |
+
maxCnt = 0
|
172 |
+
for c in composition:
|
173 |
+
if (composition[c] > maxCnt):
|
174 |
+
maxCnt = composition[c]
|
175 |
+
result = c
|
176 |
+
consensus.append(result)
|
177 |
+
consensusComposition.append(composition)
|
178 |
+
return consensus, consensusComposition
|
179 |
+
|
180 |
+
def SetConsensusAxTicks(consensus, consensusComposition, ax):
|
181 |
+
ticks = []
|
182 |
+
tickLabels = []
|
183 |
+
for i in range(len(consensus)):
|
184 |
+
ticks.append(i)
|
185 |
+
label = consensus[i]
|
186 |
+
for c in consensusComposition[i]:
|
187 |
+
if (c == label):
|
188 |
+
continue
|
189 |
+
if (consensusComposition[i][c] == consensusComposition[i][label]):
|
190 |
+
label = 'X'
|
191 |
+
break
|
192 |
+
tickLabels.append(label)
|
193 |
+
ax.set_xticks(ticks, tickLabels)
|
194 |
+
|
195 |
+
def DrawConsensusHisto(msa, color = [0, 0, 1], color_map = None, start = None, end = None, ax=None):
|
196 |
+
ax = ax or plt.gca()
|
197 |
+
consensus, consensusComposition = GetConsensus(msa, start, end)
|
198 |
+
binHeight = []
|
199 |
+
colors = []
|
200 |
+
for i in range(len(consensus)):
|
201 |
+
if (sum(consensusComposition[i].values()) == 0):
|
202 |
+
binHeight.append(0)
|
203 |
+
else:
|
204 |
+
binHeight.append( consensusComposition[i][consensus[i]] /
|
205 |
+
sum(consensusComposition[i].values()) )
|
206 |
+
colors.append([1-binHeight[i] * (1-color[0]),
|
207 |
+
1-binHeight[i] * (1-color[1]),
|
208 |
+
1-binHeight[i] * (1-color[2])]) # color-base in blue
|
209 |
+
ax.bar(x=list(range(0, len(consensus))), height=binHeight,
|
210 |
+
color = colors,
|
211 |
+
width=0.95)
|
212 |
+
ax.set(ylim=(0, 1))
|
213 |
+
ax.set(xlim=(-0.5, len(consensus)-0.5))
|
214 |
+
ax.spines['top'].set_visible(False)
|
215 |
+
ax.spines['right'].set_visible(False)
|
216 |
+
ax.spines['left'].set_visible(False)
|
217 |
+
ax.spines['bottom'].set_visible(False)
|
218 |
+
ax.get_yaxis().set_ticks([])
|
219 |
+
SetConsensusAxTicks(consensus, consensusComposition, ax)
|
220 |
+
|
221 |
+
|
222 |
+
# Code from pyseqlogo for scale font to one direction
|
223 |
+
class Scale(RendererBase):
|
224 |
+
"""Scale alphabets using affine transformation"""
|
225 |
+
|
226 |
+
def __init__(self, sx, sy=None):
|
227 |
+
self._sx = sx
|
228 |
+
self._sy = sy
|
229 |
+
|
230 |
+
def draw_path(self, renderer, gc, tpath, affine, rgbFace):
|
231 |
+
affine = Affine2D().scale(self._sx, self._sy) + affine
|
232 |
+
renderer.draw_path(gc, tpath, affine, rgbFace)
|
233 |
+
|
234 |
+
def CalculateEntropy(count):
|
235 |
+
s = sum(count)
|
236 |
+
if (s == 0):
|
237 |
+
return 0
|
238 |
+
return sum([-c/s * math.log2(c/s) for c in count])
|
239 |
+
|
240 |
+
def DrawSeqLogo(msa, color_map, alphabet_size = None, start = None, end = None, ax=None):
|
241 |
+
ax = ax or plt.gca()
|
242 |
+
fig = ax.get_figure()
|
243 |
+
start, end = GetStartEnd(msa, start, end)
|
244 |
+
|
245 |
+
alphabet_size = len(color_map) - 2 # 2 for "-" and "."
|
246 |
+
consensus, consensusComposition = GetConsensus(msa, start, end)
|
247 |
+
|
248 |
+
# Definition from https://en.wikipedia.org/wiki/Sequence_logo
|
249 |
+
# Search for appropriate height.
|
250 |
+
r = []
|
251 |
+
adjuste = 1 / math.log(2) * (alphabet_size - 1) / (2 * len(msa[0]))
|
252 |
+
for i,c in enumerate(consensus):
|
253 |
+
entropy = CalculateEntropy(list(consensusComposition[i].values()))
|
254 |
+
r.append(math.log2(alphabet_size) - (entropy + adjuste))
|
255 |
+
|
256 |
+
ax.set_xlim(-0.5, end-start+1-0.5)
|
257 |
+
ax.set_ylim(0, max(r))
|
258 |
+
|
259 |
+
fontsize = CalculateFontsize(ax.get_xlim(), ax.get_ylim(),
|
260 |
+
ax, fig, 1, end - start + 1)
|
261 |
+
|
262 |
+
lengthUnit = 1
|
263 |
+
for i,c in enumerate(consensusComposition):
|
264 |
+
prevy = 0
|
265 |
+
totalCount = sum(list(c.values()))
|
266 |
+
if (totalCount == 0):
|
267 |
+
continue
|
268 |
+
for j,item in enumerate(sorted(c.items(), key=lambda x:x[1], reverse=True)):
|
269 |
+
k = item[0]
|
270 |
+
v = item[1]
|
271 |
+
text = ax.text(x=i * lengthUnit, y=prevy, s=k, fontsize=fontsize,
|
272 |
+
va="baseline", ha="center", color = (color_map[k] if (k not in ['.','-']) else [0,0,0]),
|
273 |
+
transform=ax.transData)
|
274 |
+
|
275 |
+
height = v / totalCount * r[i]
|
276 |
+
tbox = text.get_window_extent(text._renderer).transformed(ax.transData.inverted())
|
277 |
+
scale = height / (tbox.y1 - prevy)
|
278 |
+
#print(i, j, height, scale, tbox)
|
279 |
+
prevy = prevy + height
|
280 |
+
text.set_path_effects([Scale(1.0, scale)])
|
281 |
+
ax.axis('off')
|
282 |
+
SetConsensusAxTicks(consensus, consensusComposition, ax)
|
283 |
+
|
284 |
+
# Add the annotation for the sequence alignment
|
285 |
+
# annotations: a list of numbers like [['a',0,3]]: msa[0..3] (both inclusive) is annotated as the name 'a'
|
286 |
+
def DrawAnnotation(msa, annotations, color_map=None,start = None, end = None, ax=None):
|
287 |
+
ax = ax or plt.gca()
|
288 |
+
fig = ax.get_figure()
|
289 |
+
start, end = GetStartEnd(msa, start, end)
|
290 |
+
|
291 |
+
ax.set_xlim(start - 0.5, end + 0.5)
|
292 |
+
ax.set_ylim(0, 1)
|
293 |
+
fontsize = CalculateFontsize(ax.get_xlim(), ax.get_ylim(), ax, fig, 1, end - start + 1)
|
294 |
+
|
295 |
+
for a in annotations:
|
296 |
+
text = ax.text(x=(a[1]+a[2])/2, y=0.5, s=a[0], fontsize=fontsize,
|
297 |
+
va="center", ha="center", color="black", clip_on=True)
|
298 |
+
tbox = text.get_window_extent(text._renderer).transformed(ax.transData.inverted())
|
299 |
+
# Draw the bracket
|
300 |
+
ax.plot([a[1], a[1]], [0, 1], color="black", clip_on=True)
|
301 |
+
ax.plot([a[2], a[2]], [0, 1], color="black", clip_on=True)
|
302 |
+
ax.plot([a[1], tbox.x0], [0.5, 0.5], color="black", clip_on=True)
|
303 |
+
ax.plot([tbox.x1, a[2]], [0.5, 0.5], color="black", clip_on=True)
|
304 |
+
|
305 |
+
ax.axis('off')
|
306 |
+
|
307 |
+
# Draw multipanel MSA
|
308 |
+
def DrawComplexMSA(msa, panels=[], seq_names = None, panel_height_ratios=None, panel_params=None,
|
309 |
+
color_map=None, start=None, end=None, wrap=None, figsize=None):
|
310 |
+
color_map = color_map or GetColorMap(msa=msa)
|
311 |
+
start,end = GetStartEnd(msa, start,end)
|
312 |
+
wrap = wrap or (end - start + 1)
|
313 |
+
chunks = math.ceil((end - start + 1) / wrap)
|
314 |
+
|
315 |
+
height_ratios = None
|
316 |
+
if (panel_height_ratios is None):
|
317 |
+
panel_height_ratios = []
|
318 |
+
for p in panels:
|
319 |
+
if (p == DrawMSA):
|
320 |
+
panel_height_ratios.append(len(msa))
|
321 |
+
elif (p == DrawAnnotation):
|
322 |
+
panel_height_ratios.append(0.5)
|
323 |
+
else:
|
324 |
+
panel_height_ratios.append(1)
|
325 |
+
height_ratios = panel_height_ratios * chunks
|
326 |
+
fig,axes = plt.subplots(len(panels) * chunks, 1, constrained_layout=True,
|
327 |
+
figsize=figsize, height_ratios = height_ratios)
|
328 |
+
|
329 |
+
axidx = 0
|
330 |
+
for i in range(start, end + 1, wrap):
|
331 |
+
for j,func in enumerate(panels):
|
332 |
+
extraParam = {}
|
333 |
+
if (panel_params is not None):
|
334 |
+
extraParam = panel_params[j].copy()
|
335 |
+
if func is DrawMSA:
|
336 |
+
extraParam['seq_names'] = seq_names
|
337 |
+
func(msa, color_map = color_map, start=i, end=i+wrap-1,ax=axes[axidx], **extraParam)
|
338 |
+
axidx += 1
|
339 |
+
return axes
|
src/backend/gradio_msaplot/msaplot.py
ADDED
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from __future__ import annotations
|
2 |
+
|
3 |
+
import json
|
4 |
+
from typing import Any, List, Dict, Optional, Literal, ClassVar
|
5 |
+
from gradio.components import Component
|
6 |
+
from gradio.data_classes import FileData, GradioModel
|
7 |
+
from gradio.events import Events, EventListener
|
8 |
+
|
9 |
+
import matplotlib.pyplot as plt
|
10 |
+
import io
|
11 |
+
import base64
|
12 |
+
|
13 |
+
# Import functions from msa.py
|
14 |
+
from .msa import (
|
15 |
+
DrawMSA,
|
16 |
+
DrawConsensusHisto,
|
17 |
+
DrawSeqLogo,
|
18 |
+
DrawAnnotation,
|
19 |
+
DrawComplexMSA,
|
20 |
+
GetColorMap,
|
21 |
+
)
|
22 |
+
|
23 |
+
class MSAPlotData(GradioModel):
|
24 |
+
msa: List[str]
|
25 |
+
seq_names: Optional[List[str]] = None
|
26 |
+
start: Optional[int] = None
|
27 |
+
end: Optional[int] = None
|
28 |
+
color_map: Optional[Dict[str, List[float]]] = None
|
29 |
+
plot_type: Literal["msa", "consensus", "logo", "annotation", "complex"] = "msa"
|
30 |
+
panels: List[str] = ["msa"]
|
31 |
+
panel_height_ratios: Optional[List[float]] = None
|
32 |
+
panel_params: Optional[List[Dict[str, Any]]] = None
|
33 |
+
wrap: Optional[int] = None
|
34 |
+
figsize: Optional[List[float]] = None
|
35 |
+
annotations: Optional[List[List[Any]]] = None
|
36 |
+
|
37 |
+
EVENTS: ClassVar[List[Events | EventListener]] = []
|
38 |
+
|
39 |
+
def __init__(self, data: Any = None, **kwargs):
|
40 |
+
super().__init__(**kwargs)
|
41 |
+
if data is not None:
|
42 |
+
self.__dict__.update(data)
|
43 |
+
|
44 |
+
@classmethod
|
45 |
+
def get_events(cls) -> Dict[str, Any]:
|
46 |
+
return {} # MSAPlotData has no events
|
47 |
+
|
48 |
+
@classmethod
|
49 |
+
def get_description(cls) -> str:
|
50 |
+
return "Helper class for MSAPlot data"
|
51 |
+
|
52 |
+
class MSAPlot(Component):
|
53 |
+
"""
|
54 |
+
Creates a Multiple Sequence Alignment (MSA) plot component.
|
55 |
+
"""
|
56 |
+
|
57 |
+
# EVENTS = {
|
58 |
+
# "change": None,
|
59 |
+
# "clear": None
|
60 |
+
# }
|
61 |
+
EVENTS = [
|
62 |
+
Events.change,
|
63 |
+
EventListener("clear", doc="Triggered when the plot is cleared.")
|
64 |
+
]
|
65 |
+
|
66 |
+
data_model = MSAPlotData
|
67 |
+
def __init__(
|
68 |
+
self,
|
69 |
+
value: Any | None = None,
|
70 |
+
*,
|
71 |
+
label: str | None = None,
|
72 |
+
every: float | None = None,
|
73 |
+
show_label: bool | None = None,
|
74 |
+
container: bool = True,
|
75 |
+
scale: int | None = None,
|
76 |
+
min_width: int = 160,
|
77 |
+
visible: bool = True,
|
78 |
+
elem_id: str | None = None,
|
79 |
+
elem_classes: list[str] | str | None = None,
|
80 |
+
render: bool = True,
|
81 |
+
key: int | str | None = None,
|
82 |
+
):
|
83 |
+
super().__init__(
|
84 |
+
label=label,
|
85 |
+
every=every,
|
86 |
+
show_label=show_label,
|
87 |
+
container=container,
|
88 |
+
scale=scale,
|
89 |
+
min_width=min_width,
|
90 |
+
visible=visible,
|
91 |
+
elem_id=elem_id,
|
92 |
+
elem_classes=elem_classes,
|
93 |
+
render=render,
|
94 |
+
key=key,
|
95 |
+
value=value,
|
96 |
+
)
|
97 |
+
|
98 |
+
def preprocess(self, payload: MSAPlotData | None) -> MSAPlotData | None:
|
99 |
+
return payload
|
100 |
+
|
101 |
+
def postprocess(self, value: MSAPlotData) -> Dict[str, Any]:
|
102 |
+
if value is None:
|
103 |
+
return None
|
104 |
+
|
105 |
+
fig, ax = plt.subplots(figsize=value.figsize or (10, 5))
|
106 |
+
|
107 |
+
color_map = value.color_map or GetColorMap(msa=value.msa)
|
108 |
+
|
109 |
+
if value.plot_type == "msa":
|
110 |
+
DrawMSA(value.msa, seq_names=value.seq_names, start=value.start, end=value.end, color_map=color_map, ax=ax)
|
111 |
+
elif value.plot_type == "consensus":
|
112 |
+
DrawConsensusHisto(value.msa, color_map=color_map, start=value.start, end=value.end, ax=ax)
|
113 |
+
elif value.plot_type == "logo":
|
114 |
+
DrawSeqLogo(value.msa, color_map=color_map, start=value.start, end=value.end, ax=ax)
|
115 |
+
elif value.plot_type == "annotation":
|
116 |
+
if value.annotations:
|
117 |
+
DrawAnnotation(value.msa, value.annotations, color_map=color_map, start=value.start, end=value.end, ax=ax)
|
118 |
+
else:
|
119 |
+
raise ValueError("Annotations are required for annotation plot type")
|
120 |
+
elif value.plot_type == "complex":
|
121 |
+
panel_functions = {
|
122 |
+
"msa": DrawMSA,
|
123 |
+
"consensus": DrawConsensusHisto,
|
124 |
+
"logo": DrawSeqLogo,
|
125 |
+
"annotation": DrawAnnotation,
|
126 |
+
}
|
127 |
+
panels = [panel_functions[p] for p in value.panels]
|
128 |
+
DrawComplexMSA(
|
129 |
+
value.msa,
|
130 |
+
panels=panels,
|
131 |
+
seq_names=value.seq_names,
|
132 |
+
panel_height_ratios=value.panel_height_ratios,
|
133 |
+
panel_params=value.panel_params,
|
134 |
+
color_map=color_map,
|
135 |
+
start=value.start,
|
136 |
+
end=value.end,
|
137 |
+
wrap=value.wrap,
|
138 |
+
figsize=value.figsize,
|
139 |
+
)
|
140 |
+
|
141 |
+
buf = io.BytesIO()
|
142 |
+
plt.savefig(buf, format='png')
|
143 |
+
buf.seek(0)
|
144 |
+
img_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
|
145 |
+
plt.close(fig)
|
146 |
+
|
147 |
+
return {
|
148 |
+
"type": "matplotlib",
|
149 |
+
"plot": f"data:image/png;base64,{img_base64}",
|
150 |
+
}
|
151 |
+
|
152 |
+
def example_payload(self) -> Any:
|
153 |
+
return MSAPlotData(
|
154 |
+
msa=[
|
155 |
+
"ATGCATGC",
|
156 |
+
"ATG-ATGC",
|
157 |
+
"ATGCATGC",
|
158 |
+
],
|
159 |
+
seq_names=["Seq1", "Seq2", "Seq3"],
|
160 |
+
plot_type="complex",
|
161 |
+
panels=["msa", "consensus", "logo"],
|
162 |
+
)
|
163 |
+
|
164 |
+
def example_value(self) -> Any:
|
165 |
+
return self.example_payload()
|
166 |
+
|
167 |
+
@classmethod
|
168 |
+
def get_events(cls) -> Dict[str, Any]:
|
169 |
+
return {event.value if isinstance(event, Events) else event.name: event.doc for event in cls.EVENTS}
|
170 |
+
|
171 |
+
@classmethod
|
172 |
+
def get_description(cls) -> str:
|
173 |
+
return "Creates a Multiple Sequence Alignment (MSA) plot component."
|
174 |
+
|
175 |
+
def api_info(self) -> Dict[str, Any]:
|
176 |
+
return {
|
177 |
+
"type": "object",
|
178 |
+
"properties": {
|
179 |
+
"msa": {
|
180 |
+
"type": "array",
|
181 |
+
"items": {"type": "string"},
|
182 |
+
"description": "List of sequences in the multiple sequence alignment",
|
183 |
+
},
|
184 |
+
"seq_names": {
|
185 |
+
"type": "array",
|
186 |
+
"items": {"type": "string"},
|
187 |
+
"description": "List of sequence names",
|
188 |
+
},
|
189 |
+
"plot_type": {
|
190 |
+
"type": "string",
|
191 |
+
"enum": ["msa", "consensus", "logo", "annotation", "complex"],
|
192 |
+
"description": "Type of plot to generate",
|
193 |
+
},
|
194 |
+
"panels": {
|
195 |
+
"type": "array",
|
196 |
+
"items": {"type": "string"},
|
197 |
+
"description": "List of panels to include in a complex plot",
|
198 |
+
},
|
199 |
+
},
|
200 |
+
"required": ["msa"],
|
201 |
+
}
|
src/demo/__init__.py
ADDED
File without changes
|
src/demo/app.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import gradio as gr
|
3 |
+
from gradio_msaplot import MSAPlot, MSAPlotData
|
4 |
+
import matplotlib
|
5 |
+
matplotlib.use('Agg')
|
6 |
+
|
7 |
+
example = MSAPlot().example_value()
|
8 |
+
|
9 |
+
with gr.Blocks() as demo:
|
10 |
+
with gr.Row():
|
11 |
+
MSAPlot(label="Blank"), # blank component
|
12 |
+
MSAPlot(value=example, label="Populated"), # populated component
|
13 |
+
|
14 |
+
|
15 |
+
if __name__ == "__main__":
|
16 |
+
demo.launch()
|
src/demo/css.css
ADDED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
html {
|
2 |
+
font-family: Inter;
|
3 |
+
font-size: 16px;
|
4 |
+
font-weight: 400;
|
5 |
+
line-height: 1.5;
|
6 |
+
-webkit-text-size-adjust: 100%;
|
7 |
+
background: #fff;
|
8 |
+
color: #323232;
|
9 |
+
-webkit-font-smoothing: antialiased;
|
10 |
+
-moz-osx-font-smoothing: grayscale;
|
11 |
+
text-rendering: optimizeLegibility;
|
12 |
+
}
|
13 |
+
|
14 |
+
:root {
|
15 |
+
--space: 1;
|
16 |
+
--vspace: calc(var(--space) * 1rem);
|
17 |
+
--vspace-0: calc(3 * var(--space) * 1rem);
|
18 |
+
--vspace-1: calc(2 * var(--space) * 1rem);
|
19 |
+
--vspace-2: calc(1.5 * var(--space) * 1rem);
|
20 |
+
--vspace-3: calc(0.5 * var(--space) * 1rem);
|
21 |
+
}
|
22 |
+
|
23 |
+
.app {
|
24 |
+
max-width: 748px !important;
|
25 |
+
}
|
26 |
+
|
27 |
+
.prose p {
|
28 |
+
margin: var(--vspace) 0;
|
29 |
+
line-height: var(--vspace * 2);
|
30 |
+
font-size: 1rem;
|
31 |
+
}
|
32 |
+
|
33 |
+
code {
|
34 |
+
font-family: "Inconsolata", sans-serif;
|
35 |
+
font-size: 16px;
|
36 |
+
}
|
37 |
+
|
38 |
+
h1,
|
39 |
+
h1 code {
|
40 |
+
font-weight: 400;
|
41 |
+
line-height: calc(2.5 / var(--space) * var(--vspace));
|
42 |
+
}
|
43 |
+
|
44 |
+
h1 code {
|
45 |
+
background: none;
|
46 |
+
border: none;
|
47 |
+
letter-spacing: 0.05em;
|
48 |
+
padding-bottom: 5px;
|
49 |
+
position: relative;
|
50 |
+
padding: 0;
|
51 |
+
}
|
52 |
+
|
53 |
+
h2 {
|
54 |
+
margin: var(--vspace-1) 0 var(--vspace-2) 0;
|
55 |
+
line-height: 1em;
|
56 |
+
}
|
57 |
+
|
58 |
+
h3,
|
59 |
+
h3 code {
|
60 |
+
margin: var(--vspace-1) 0 var(--vspace-2) 0;
|
61 |
+
line-height: 1em;
|
62 |
+
}
|
63 |
+
|
64 |
+
h4,
|
65 |
+
h5,
|
66 |
+
h6 {
|
67 |
+
margin: var(--vspace-3) 0 var(--vspace-3) 0;
|
68 |
+
line-height: var(--vspace);
|
69 |
+
}
|
70 |
+
|
71 |
+
.bigtitle,
|
72 |
+
h1,
|
73 |
+
h1 code {
|
74 |
+
font-size: calc(8px * 4.5);
|
75 |
+
word-break: break-word;
|
76 |
+
}
|
77 |
+
|
78 |
+
.title,
|
79 |
+
h2,
|
80 |
+
h2 code {
|
81 |
+
font-size: calc(8px * 3.375);
|
82 |
+
font-weight: lighter;
|
83 |
+
word-break: break-word;
|
84 |
+
border: none;
|
85 |
+
background: none;
|
86 |
+
}
|
87 |
+
|
88 |
+
.subheading1,
|
89 |
+
h3,
|
90 |
+
h3 code {
|
91 |
+
font-size: calc(8px * 1.8);
|
92 |
+
font-weight: 600;
|
93 |
+
border: none;
|
94 |
+
background: none;
|
95 |
+
letter-spacing: 0.1em;
|
96 |
+
text-transform: uppercase;
|
97 |
+
}
|
98 |
+
|
99 |
+
h2 code {
|
100 |
+
padding: 0;
|
101 |
+
position: relative;
|
102 |
+
letter-spacing: 0.05em;
|
103 |
+
}
|
104 |
+
|
105 |
+
blockquote {
|
106 |
+
font-size: calc(8px * 1.1667);
|
107 |
+
font-style: italic;
|
108 |
+
line-height: calc(1.1667 * var(--vspace));
|
109 |
+
margin: var(--vspace-2) var(--vspace-2);
|
110 |
+
}
|
111 |
+
|
112 |
+
.subheading2,
|
113 |
+
h4 {
|
114 |
+
font-size: calc(8px * 1.4292);
|
115 |
+
text-transform: uppercase;
|
116 |
+
font-weight: 600;
|
117 |
+
}
|
118 |
+
|
119 |
+
.subheading3,
|
120 |
+
h5 {
|
121 |
+
font-size: calc(8px * 1.2917);
|
122 |
+
line-height: calc(1.2917 * var(--vspace));
|
123 |
+
|
124 |
+
font-weight: lighter;
|
125 |
+
text-transform: uppercase;
|
126 |
+
letter-spacing: 0.15em;
|
127 |
+
}
|
128 |
+
|
129 |
+
h6 {
|
130 |
+
font-size: calc(8px * 1.1667);
|
131 |
+
font-size: 1.1667em;
|
132 |
+
font-weight: normal;
|
133 |
+
font-style: italic;
|
134 |
+
font-family: "le-monde-livre-classic-byol", serif !important;
|
135 |
+
letter-spacing: 0px !important;
|
136 |
+
}
|
137 |
+
|
138 |
+
#start .md > *:first-child {
|
139 |
+
margin-top: 0;
|
140 |
+
}
|
141 |
+
|
142 |
+
h2 + h3 {
|
143 |
+
margin-top: 0;
|
144 |
+
}
|
145 |
+
|
146 |
+
.md hr {
|
147 |
+
border: none;
|
148 |
+
border-top: 1px solid var(--block-border-color);
|
149 |
+
margin: var(--vspace-2) 0 var(--vspace-2) 0;
|
150 |
+
}
|
151 |
+
.prose ul {
|
152 |
+
margin: var(--vspace-2) 0 var(--vspace-1) 0;
|
153 |
+
}
|
154 |
+
|
155 |
+
.gap {
|
156 |
+
gap: 0;
|
157 |
+
}
|
src/demo/requirements.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
gradio_msaplot
|
src/demo/space.py
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import gradio as gr
|
3 |
+
from app import demo as app
|
4 |
+
import os
|
5 |
+
|
6 |
+
_docs = {'MSAPlot': {'description': 'Creates a Multiple Sequence Alignment (MSA) plot component.', 'members': {'__init__': {'value': {'type': 'typing.Any | None', 'default': 'None', 'description': None}, 'label': {'type': 'str | None', 'default': 'None', 'description': None}, 'every': {'type': 'float | None', 'default': 'None', 'description': None}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': None}, 'container': {'type': 'bool', 'default': 'True', 'description': None}, 'scale': {'type': 'int | None', 'default': 'None', 'description': None}, 'min_width': {'type': 'int', 'default': '160', 'description': None}, 'visible': {'type': 'bool', 'default': 'True', 'description': None}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': None}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': None}, 'render': {'type': 'bool', 'default': 'True', 'description': None}, 'key': {'type': 'int | str | None', 'default': 'None', 'description': None}}, 'postprocess': {'value': {'type': 'MSAPlotData', 'description': "The output data received by the component from the user's function in the backend."}}, 'preprocess': {'return': {'type': 'MSAPlotData | None', 'description': "The preprocessed input data sent to the user's function in the backend."}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the MSAPlot changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'clear': {'type': None, 'default': None, 'description': 'Triggered when the plot is cleared.'}}}, '__meta__': {'additional_interfaces': {}}, 'MSAPlotData': {'description': 'Helper class that provides a standard way to create an ABC using\ninheritance.', 'members': {'__init__': {'data': {'type': 'typing.Any', 'default': 'None', 'description': None}}}, 'events': {}}}
|
7 |
+
|
8 |
+
abs_path = os.path.join(os.path.dirname(__file__), "css.css")
|
9 |
+
|
10 |
+
with gr.Blocks(
|
11 |
+
css=abs_path,
|
12 |
+
theme=gr.themes.Default(
|
13 |
+
font_mono=[
|
14 |
+
gr.themes.GoogleFont("Inconsolata"),
|
15 |
+
"monospace",
|
16 |
+
],
|
17 |
+
),
|
18 |
+
) as demo:
|
19 |
+
gr.Markdown(
|
20 |
+
"""
|
21 |
+
# `gradio_msaplot`
|
22 |
+
|
23 |
+
<div style="display: flex; gap: 7px;">
|
24 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange"> <a href="https://github.com/Josephrp/GradioMSAPlot.git/issues" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/Issues-white?logo=github&logoColor=black"></a> <a href="https://huggingface.co/spaces/seq-to-pheno/MSAPlot/discussions" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/%F0%9F%A4%97%20Discuss-%23097EFF?style=flat&logoColor=black"></a>
|
25 |
+
</div>
|
26 |
+
|
27 |
+
MSAplot is customizable panels for plotting MSA, seqlogo, annotation, and consensus histograms.
|
28 |
+
""", elem_classes=["md-custom"], header_links=True)
|
29 |
+
app.render()
|
30 |
+
gr.Markdown(
|
31 |
+
"""
|
32 |
+
## Installation
|
33 |
+
|
34 |
+
```bash
|
35 |
+
pip install gradio_msaplot
|
36 |
+
```
|
37 |
+
|
38 |
+
## Usage
|
39 |
+
|
40 |
+
```python
|
41 |
+
|
42 |
+
import gradio as gr
|
43 |
+
from gradio_msaplot import MSAPlot, MSAPlotData
|
44 |
+
import matplotlib
|
45 |
+
matplotlib.use('Agg')
|
46 |
+
|
47 |
+
example = MSAPlot().example_value()
|
48 |
+
|
49 |
+
with gr.Blocks() as demo:
|
50 |
+
with gr.Row():
|
51 |
+
MSAPlot(label="Blank"), # blank component
|
52 |
+
MSAPlot(value=example, label="Populated"), # populated component
|
53 |
+
|
54 |
+
|
55 |
+
if __name__ == "__main__":
|
56 |
+
demo.launch()
|
57 |
+
|
58 |
+
```
|
59 |
+
""", elem_classes=["md-custom"], header_links=True)
|
60 |
+
|
61 |
+
|
62 |
+
gr.Markdown("""
|
63 |
+
## `MSAPlot`
|
64 |
+
|
65 |
+
### Initialization
|
66 |
+
""", elem_classes=["md-custom"], header_links=True)
|
67 |
+
|
68 |
+
gr.ParamViewer(value=_docs["MSAPlot"]["members"]["__init__"], linkify=[])
|
69 |
+
|
70 |
+
|
71 |
+
gr.Markdown("### Events")
|
72 |
+
gr.ParamViewer(value=_docs["MSAPlot"]["events"], linkify=['Event'])
|
73 |
+
|
74 |
+
|
75 |
+
|
76 |
+
|
77 |
+
gr.Markdown("""
|
78 |
+
|
79 |
+
### User function
|
80 |
+
|
81 |
+
The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
|
82 |
+
|
83 |
+
- When used as an Input, the component only impacts the input signature of the user function.
|
84 |
+
- When used as an output, the component only impacts the return signature of the user function.
|
85 |
+
|
86 |
+
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
87 |
+
|
88 |
+
- **As input:** Is passed, the preprocessed input data sent to the user's function in the backend.
|
89 |
+
- **As output:** Should return, the output data received by the component from the user's function in the backend.
|
90 |
+
|
91 |
+
```python
|
92 |
+
def predict(
|
93 |
+
value: MSAPlotData | None
|
94 |
+
) -> MSAPlotData:
|
95 |
+
return value
|
96 |
+
```
|
97 |
+
""", elem_classes=["md-custom", "MSAPlot-user-fn"], header_links=True)
|
98 |
+
|
99 |
+
|
100 |
+
gr.Markdown("""
|
101 |
+
## `MSAPlotData`
|
102 |
+
|
103 |
+
### Initialization
|
104 |
+
""", elem_classes=["md-custom"], header_links=True)
|
105 |
+
|
106 |
+
gr.ParamViewer(value=_docs["MSAPlotData"]["members"]["__init__"], linkify=[])
|
107 |
+
|
108 |
+
|
109 |
+
|
110 |
+
|
111 |
+
|
112 |
+
|
113 |
+
|
114 |
+
demo.load(None, js=r"""function() {
|
115 |
+
const refs = {};
|
116 |
+
const user_fn_refs = {};
|
117 |
+
requestAnimationFrame(() => {
|
118 |
+
|
119 |
+
Object.entries(user_fn_refs).forEach(([key, refs]) => {
|
120 |
+
if (refs.length > 0) {
|
121 |
+
const el = document.querySelector(`.${key}-user-fn`);
|
122 |
+
if (!el) return;
|
123 |
+
refs.forEach(ref => {
|
124 |
+
el.innerHTML = el.innerHTML.replace(
|
125 |
+
new RegExp("\\b"+ref+"\\b", "g"),
|
126 |
+
`<a href="#h-${ref.toLowerCase()}">${ref}</a>`
|
127 |
+
);
|
128 |
+
})
|
129 |
+
}
|
130 |
+
})
|
131 |
+
|
132 |
+
Object.entries(refs).forEach(([key, refs]) => {
|
133 |
+
if (refs.length > 0) {
|
134 |
+
const el = document.querySelector(`.${key}`);
|
135 |
+
if (!el) return;
|
136 |
+
refs.forEach(ref => {
|
137 |
+
el.innerHTML = el.innerHTML.replace(
|
138 |
+
new RegExp("\\b"+ref+"\\b", "g"),
|
139 |
+
`<a href="#h-${ref.toLowerCase()}">${ref}</a>`
|
140 |
+
);
|
141 |
+
})
|
142 |
+
}
|
143 |
+
})
|
144 |
+
})
|
145 |
+
}
|
146 |
+
|
147 |
+
""")
|
148 |
+
|
149 |
+
demo.launch()
|
src/frontend/Index.svelte
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import type { LoadingStatus } from "@gradio/statustracker";
|
3 |
+
import { Block } from "@gradio/atoms";
|
4 |
+
import { StatusTracker } from "@gradio/statustracker";
|
5 |
+
import type { Gradio } from "@gradio/utils";
|
6 |
+
import { Plot as PlotIcon } from "@gradio/icons";
|
7 |
+
|
8 |
+
export let gradio: Gradio<{
|
9 |
+
change: never;
|
10 |
+
clear: never;
|
11 |
+
}>;
|
12 |
+
|
13 |
+
export let value: {
|
14 |
+
type: string;
|
15 |
+
plot: string;
|
16 |
+
} | null = null;
|
17 |
+
export let elem_id = "";
|
18 |
+
export let elem_classes: string[] = [];
|
19 |
+
export let scale: number | null = null;
|
20 |
+
export let min_width: number | undefined = undefined;
|
21 |
+
export let loading_status: LoadingStatus | undefined = undefined;
|
22 |
+
export let mode: "static" | "interactive" = "interactive";
|
23 |
+
export let label: string | undefined = undefined;
|
24 |
+
export let show_label = true;
|
25 |
+
export let container = true;
|
26 |
+
export let visible = true;
|
27 |
+
|
28 |
+
$: src = value?.plot || "";
|
29 |
+
</script>
|
30 |
+
|
31 |
+
<Block
|
32 |
+
{visible}
|
33 |
+
{elem_id}
|
34 |
+
{elem_classes}
|
35 |
+
{scale}
|
36 |
+
{min_width}
|
37 |
+
allow_overflow={false}
|
38 |
+
padding={true}
|
39 |
+
{container}
|
40 |
+
>
|
41 |
+
{#if show_label}
|
42 |
+
<label for={elem_id}>{label || "MSA Plot"}</label>
|
43 |
+
{/if}
|
44 |
+
|
45 |
+
{#if loading_status}
|
46 |
+
<StatusTracker
|
47 |
+
autoscroll={gradio.autoscroll}
|
48 |
+
i18n={gradio.i18n}
|
49 |
+
{...loading_status}
|
50 |
+
/>
|
51 |
+
{/if}
|
52 |
+
|
53 |
+
{#if src}
|
54 |
+
<img {src} alt="MSA Plot" style="width: 100%; height: auto;" />
|
55 |
+
{:else}
|
56 |
+
<div class="placeholder">
|
57 |
+
<PlotIcon />
|
58 |
+
<p>{gradio.i18n("plot.no_plot")}</p>
|
59 |
+
</div>
|
60 |
+
{/if}
|
61 |
+
</Block>
|
62 |
+
|
63 |
+
<style>
|
64 |
+
.placeholder {
|
65 |
+
display: flex;
|
66 |
+
flex-direction: column;
|
67 |
+
align-items: center;
|
68 |
+
justify-content: center;
|
69 |
+
height: 100%;
|
70 |
+
color: var(--color-text-subdued);
|
71 |
+
}
|
72 |
+
</style>
|
src/frontend/gradio.config.js
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export default {
|
2 |
+
plugins: [],
|
3 |
+
svelte: {
|
4 |
+
preprocess: [],
|
5 |
+
},
|
6 |
+
build: {
|
7 |
+
target: "modules",
|
8 |
+
},
|
9 |
+
};
|
src/frontend/package-lock.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
src/frontend/package.json
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "gradio_msaplot",
|
3 |
+
"version": "0.7.1",
|
4 |
+
"description": "Gradio UI packages",
|
5 |
+
"type": "module",
|
6 |
+
"author": "",
|
7 |
+
"license": "ISC",
|
8 |
+
"private": false,
|
9 |
+
"dependencies": {
|
10 |
+
"@gradio/atoms": "0.9.0",
|
11 |
+
"@gradio/icons": "0.8.0",
|
12 |
+
"@gradio/statustracker": "0.8.1",
|
13 |
+
"@gradio/theme": "0.3.0",
|
14 |
+
"@gradio/utils": "0.7.0",
|
15 |
+
"@rollup/plugin-json": "^6.0.0",
|
16 |
+
"plotly.js-dist-min": "^2.10.1",
|
17 |
+
"vega": "^5.23.0",
|
18 |
+
"vega-embed": "^6.25.0",
|
19 |
+
"vega-lite": "^5.12.0"
|
20 |
+
},
|
21 |
+
"devDependencies": {
|
22 |
+
"@gradio/preview": "0.12.0"
|
23 |
+
},
|
24 |
+
"main": "./Index.svelte",
|
25 |
+
"main_changeset": true,
|
26 |
+
"exports": {
|
27 |
+
"./package.json": "./package.json",
|
28 |
+
".": {
|
29 |
+
"gradio": "./Index.svelte",
|
30 |
+
"svelte": "./dist/Index.svelte",
|
31 |
+
"types": "./dist/Index.svelte.d.ts"
|
32 |
+
},
|
33 |
+
"./base": {
|
34 |
+
"gradio": "./shared/Plot.svelte",
|
35 |
+
"svelte": "./dist/shared/Plot.svelte",
|
36 |
+
"types": "./dist/shared/Plot.svelte.d.ts"
|
37 |
+
}
|
38 |
+
},
|
39 |
+
"peerDependencies": {
|
40 |
+
"svelte": "^4.0.0"
|
41 |
+
},
|
42 |
+
"repository": {
|
43 |
+
"type": "git",
|
44 |
+
"url": "git+https://github.com/gradio-app/gradio.git",
|
45 |
+
"directory": "js/plot"
|
46 |
+
}
|
47 |
+
}
|
src/frontend/shared/Plot.svelte
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
//@ts-nocheck
|
3 |
+
import { Plot as PlotIcon } from "@gradio/icons";
|
4 |
+
import { Empty } from "@gradio/atoms";
|
5 |
+
import type { ThemeMode } from "js/core/src/components/types";
|
6 |
+
import type { Gradio, SelectData } from "@gradio/utils";
|
7 |
+
|
8 |
+
export let value;
|
9 |
+
export let colors: string[] = [];
|
10 |
+
export let theme_mode: ThemeMode;
|
11 |
+
export let caption: string;
|
12 |
+
export let bokeh_version: string | null;
|
13 |
+
export let show_actions_button: bool;
|
14 |
+
export let gradio: Gradio<{
|
15 |
+
select: SelectData;
|
16 |
+
}>;
|
17 |
+
export let x_lim: [number, number] | null = null;
|
18 |
+
export let _selectable: boolean;
|
19 |
+
|
20 |
+
let PlotComponent: any = null;
|
21 |
+
let _type = value?.type;
|
22 |
+
|
23 |
+
const plotTypeMapping = {
|
24 |
+
plotly: () => import("./plot_types/PlotlyPlot.svelte"),
|
25 |
+
bokeh: () => import("./plot_types/BokehPlot.svelte"),
|
26 |
+
altair: () => import("./plot_types/AltairPlot.svelte"),
|
27 |
+
matplotlib: () => import("./plot_types/MatplotlibPlot.svelte")
|
28 |
+
};
|
29 |
+
|
30 |
+
const is_browser = typeof window !== "undefined";
|
31 |
+
|
32 |
+
$: {
|
33 |
+
let type = value?.type;
|
34 |
+
if (type !== _type) {
|
35 |
+
PlotComponent = null;
|
36 |
+
}
|
37 |
+
if (type && type in plotTypeMapping && is_browser) {
|
38 |
+
plotTypeMapping[type]().then((module) => {
|
39 |
+
PlotComponent = module.default;
|
40 |
+
});
|
41 |
+
}
|
42 |
+
}
|
43 |
+
</script>
|
44 |
+
|
45 |
+
{#if value && PlotComponent}
|
46 |
+
<svelte:component
|
47 |
+
this={PlotComponent}
|
48 |
+
{value}
|
49 |
+
{colors}
|
50 |
+
{theme_mode}
|
51 |
+
{caption}
|
52 |
+
{bokeh_version}
|
53 |
+
{show_actions_button}
|
54 |
+
{gradio}
|
55 |
+
{_selectable}
|
56 |
+
{x_lim}
|
57 |
+
on:load
|
58 |
+
on:select
|
59 |
+
/>
|
60 |
+
{:else}
|
61 |
+
<Empty unpadded_box={true} size="large"><PlotIcon /></Empty>
|
62 |
+
{/if}
|
src/frontend/shared/plot_types/AltairPlot.svelte
ADDED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
//@ts-nocheck
|
3 |
+
import { set_config } from "./altair_utils";
|
4 |
+
import { onMount, onDestroy } from "svelte";
|
5 |
+
import type { TopLevelSpec as Spec } from "vega-lite";
|
6 |
+
import vegaEmbed from "vega-embed";
|
7 |
+
import type { Gradio, SelectData } from "@gradio/utils";
|
8 |
+
import type { View } from "vega";
|
9 |
+
|
10 |
+
export let value;
|
11 |
+
export let colors: string[] = [];
|
12 |
+
export let caption: string;
|
13 |
+
export let show_actions_button: bool;
|
14 |
+
export let gradio: Gradio<{
|
15 |
+
select: SelectData;
|
16 |
+
}>;
|
17 |
+
let element: HTMLElement;
|
18 |
+
let parent_element: HTMLElement;
|
19 |
+
let view: View;
|
20 |
+
export let _selectable: bool;
|
21 |
+
|
22 |
+
let computed_style = window.getComputedStyle(document.body);
|
23 |
+
|
24 |
+
let old_spec: Spec;
|
25 |
+
let spec_width: number;
|
26 |
+
$: plot = value?.plot;
|
27 |
+
$: spec = JSON.parse(plot) as Spec;
|
28 |
+
$: if (spec && spec.params && !_selectable) {
|
29 |
+
spec.params = spec.params.filter((param) => param.name !== "brush");
|
30 |
+
}
|
31 |
+
$: if (old_spec !== spec) {
|
32 |
+
old_spec = spec;
|
33 |
+
spec_width = spec.width;
|
34 |
+
}
|
35 |
+
|
36 |
+
$: if (value.chart) {
|
37 |
+
spec = set_config(spec, computed_style, value.chart as string, colors);
|
38 |
+
}
|
39 |
+
$: fit_width_to_parent =
|
40 |
+
spec.encoding?.column?.field ||
|
41 |
+
spec.encoding?.row?.field ||
|
42 |
+
value.chart === undefined
|
43 |
+
? false
|
44 |
+
: true; // vega seems to glitch with width when orientation is set
|
45 |
+
|
46 |
+
const get_width = (): number => {
|
47 |
+
return Math.min(
|
48 |
+
parent_element.offsetWidth,
|
49 |
+
spec_width || parent_element.offsetWidth
|
50 |
+
);
|
51 |
+
};
|
52 |
+
let resize_callback = (): void => {};
|
53 |
+
const renderPlot = (): void => {
|
54 |
+
if (fit_width_to_parent) {
|
55 |
+
spec.width = get_width();
|
56 |
+
}
|
57 |
+
vegaEmbed(element, spec, { actions: show_actions_button }).then(
|
58 |
+
function (result): void {
|
59 |
+
view = result.view;
|
60 |
+
resize_callback = () => {
|
61 |
+
view.signal("width", get_width()).run();
|
62 |
+
};
|
63 |
+
|
64 |
+
if (!_selectable) return;
|
65 |
+
const callback = (event, item): void => {
|
66 |
+
const brushValue = view.signal("brush");
|
67 |
+
if (brushValue) {
|
68 |
+
if (Object.keys(brushValue).length === 0) {
|
69 |
+
gradio.dispatch("select", {
|
70 |
+
value: null,
|
71 |
+
index: null,
|
72 |
+
selected: false
|
73 |
+
});
|
74 |
+
} else {
|
75 |
+
const key = Object.keys(brushValue)[0];
|
76 |
+
let range: [number, number] = brushValue[key].map(
|
77 |
+
(x) => x / 1000
|
78 |
+
);
|
79 |
+
gradio.dispatch("select", {
|
80 |
+
value: brushValue,
|
81 |
+
index: range,
|
82 |
+
selected: true
|
83 |
+
});
|
84 |
+
}
|
85 |
+
}
|
86 |
+
};
|
87 |
+
view.addEventListener("mouseup", callback);
|
88 |
+
view.addEventListener("touchup", callback);
|
89 |
+
}
|
90 |
+
);
|
91 |
+
};
|
92 |
+
let resizeObserver = new ResizeObserver(() => {
|
93 |
+
if (fit_width_to_parent && spec.width !== parent_element.offsetWidth) {
|
94 |
+
resize_callback();
|
95 |
+
}
|
96 |
+
});
|
97 |
+
onMount(() => {
|
98 |
+
renderPlot();
|
99 |
+
resizeObserver.observe(parent_element);
|
100 |
+
});
|
101 |
+
onDestroy(() => {
|
102 |
+
resizeObserver.disconnect();
|
103 |
+
});
|
104 |
+
</script>
|
105 |
+
|
106 |
+
<div data-testid={"altair"} class="altair layout" bind:this={parent_element}>
|
107 |
+
<div bind:this={element}></div>
|
108 |
+
{#if caption}
|
109 |
+
<div class="caption layout">
|
110 |
+
{caption}
|
111 |
+
</div>
|
112 |
+
{/if}
|
113 |
+
</div>
|
114 |
+
|
115 |
+
<style>
|
116 |
+
.altair :global(canvas) {
|
117 |
+
padding: 6px;
|
118 |
+
}
|
119 |
+
.altair :global(.vega-embed) {
|
120 |
+
padding: 0px !important;
|
121 |
+
}
|
122 |
+
.altair :global(.vega-actions) {
|
123 |
+
right: 0px !important;
|
124 |
+
}
|
125 |
+
.layout {
|
126 |
+
display: flex;
|
127 |
+
flex-direction: column;
|
128 |
+
justify-content: center;
|
129 |
+
align-items: center;
|
130 |
+
width: var(--size-full);
|
131 |
+
height: var(--size-full);
|
132 |
+
color: var(--body-text-color);
|
133 |
+
}
|
134 |
+
.altair {
|
135 |
+
display: flex;
|
136 |
+
flex-direction: column;
|
137 |
+
justify-content: center;
|
138 |
+
align-items: center;
|
139 |
+
width: var(--size-full);
|
140 |
+
height: var(--size-full);
|
141 |
+
}
|
142 |
+
.caption {
|
143 |
+
font-size: var(--text-sm);
|
144 |
+
margin-bottom: 6px;
|
145 |
+
}
|
146 |
+
:global(#vg-tooltip-element) {
|
147 |
+
font-family: var(--font) !important;
|
148 |
+
font-size: var(--text-xs) !important;
|
149 |
+
box-shadow: none !important;
|
150 |
+
background-color: var(--block-background-fill) !important;
|
151 |
+
border: 1px solid var(--border-color-primary) !important;
|
152 |
+
color: var(--body-text-color) !important;
|
153 |
+
}
|
154 |
+
:global(#vg-tooltip-element .key) {
|
155 |
+
color: var(--body-text-color-subdued) !important;
|
156 |
+
}
|
157 |
+
</style>
|
src/frontend/shared/plot_types/BokehPlot.svelte
ADDED
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
//@ts-nocheck
|
3 |
+
import { onDestroy, createEventDispatcher } from "svelte";
|
4 |
+
|
5 |
+
export let value;
|
6 |
+
export let bokeh_version: string | null;
|
7 |
+
const div_id = `bokehDiv-${Math.random().toString(5).substring(2)}`;
|
8 |
+
const dispatch = createEventDispatcher<{ load: undefined }>();
|
9 |
+
$: plot = value?.plot;
|
10 |
+
|
11 |
+
async function embed_bokeh(_plot: Record<string, any>): void {
|
12 |
+
if (document) {
|
13 |
+
if (document.getElementById(div_id)) {
|
14 |
+
document.getElementById(div_id).innerHTML = "";
|
15 |
+
}
|
16 |
+
}
|
17 |
+
if (window.Bokeh) {
|
18 |
+
load_bokeh();
|
19 |
+
let plotObj = JSON.parse(_plot);
|
20 |
+
const y = await window.Bokeh.embed.embed_item(plotObj, div_id);
|
21 |
+
y._roots.forEach(async (p) => {
|
22 |
+
await p.ready;
|
23 |
+
dispatch("load");
|
24 |
+
});
|
25 |
+
}
|
26 |
+
}
|
27 |
+
|
28 |
+
$: loaded && embed_bokeh(plot);
|
29 |
+
|
30 |
+
const main_src = `https://cdn.bokeh.org/bokeh/release/bokeh-${bokeh_version}.min.js`;
|
31 |
+
|
32 |
+
const plugins_src = [
|
33 |
+
`https://cdn.pydata.org/bokeh/release/bokeh-widgets-${bokeh_version}.min.js`,
|
34 |
+
`https://cdn.pydata.org/bokeh/release/bokeh-tables-${bokeh_version}.min.js`,
|
35 |
+
`https://cdn.pydata.org/bokeh/release/bokeh-gl-${bokeh_version}.min.js`,
|
36 |
+
`https://cdn.pydata.org/bokeh/release/bokeh-api-${bokeh_version}.min.js`
|
37 |
+
];
|
38 |
+
|
39 |
+
let loaded = false;
|
40 |
+
async function load_plugins(): HTMLScriptElement[] {
|
41 |
+
await Promise.all(
|
42 |
+
plugins_src.map((src, i) => {
|
43 |
+
return new Promise((resolve) => {
|
44 |
+
const script = document.createElement("script");
|
45 |
+
script.onload = resolve;
|
46 |
+
script.src = src;
|
47 |
+
document.head.appendChild(script);
|
48 |
+
return script;
|
49 |
+
});
|
50 |
+
})
|
51 |
+
);
|
52 |
+
|
53 |
+
loaded = true;
|
54 |
+
}
|
55 |
+
|
56 |
+
let plugin_scripts = [];
|
57 |
+
|
58 |
+
function handle_bokeh_loaded(): void {
|
59 |
+
plugin_scripts = load_plugins();
|
60 |
+
}
|
61 |
+
|
62 |
+
function load_bokeh(): HTMLScriptElement {
|
63 |
+
const script = document.createElement("script");
|
64 |
+
script.onload = handle_bokeh_loaded;
|
65 |
+
script.src = main_src;
|
66 |
+
const is_bokeh_script_present = document.head.querySelector(
|
67 |
+
`script[src="${main_src}"]`
|
68 |
+
);
|
69 |
+
if (!is_bokeh_script_present) {
|
70 |
+
document.head.appendChild(script);
|
71 |
+
} else {
|
72 |
+
handle_bokeh_loaded();
|
73 |
+
}
|
74 |
+
return script;
|
75 |
+
}
|
76 |
+
|
77 |
+
const main_script = bokeh_version ? load_bokeh() : null;
|
78 |
+
|
79 |
+
onDestroy(() => {
|
80 |
+
if (main_script in document.children) {
|
81 |
+
document.removeChild(main_script);
|
82 |
+
plugin_scripts.forEach((child) => document.removeChild(child));
|
83 |
+
}
|
84 |
+
});
|
85 |
+
</script>
|
86 |
+
|
87 |
+
<div data-testid={"bokeh"} id={div_id} class="gradio-bokeh" />
|
88 |
+
|
89 |
+
<style>
|
90 |
+
.gradio-bokeh {
|
91 |
+
display: flex;
|
92 |
+
justify-content: center;
|
93 |
+
}
|
94 |
+
</style>
|
src/frontend/shared/plot_types/MatplotlibPlot.svelte
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let value;
|
3 |
+
|
4 |
+
$: plot = value?.plot;
|
5 |
+
</script>
|
6 |
+
|
7 |
+
<div data-testid={"matplotlib"} class="matplotlib layout">
|
8 |
+
<img
|
9 |
+
src={plot}
|
10 |
+
alt={`${value.chart} plot visualising provided data`}
|
11 |
+
on:load
|
12 |
+
/>
|
13 |
+
</div>
|
14 |
+
|
15 |
+
<style>
|
16 |
+
.layout {
|
17 |
+
display: flex;
|
18 |
+
flex-direction: column;
|
19 |
+
justify-content: center;
|
20 |
+
align-items: center;
|
21 |
+
width: var(--size-full);
|
22 |
+
height: var(--size-full);
|
23 |
+
color: var(--body-text-color);
|
24 |
+
}
|
25 |
+
.matplotlib img {
|
26 |
+
object-fit: contain;
|
27 |
+
}
|
28 |
+
</style>
|
src/frontend/shared/plot_types/PlotlyPlot.svelte
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
//@ts-nocheck
|
3 |
+
import Plotly from "plotly.js-dist-min";
|
4 |
+
import { afterUpdate, createEventDispatcher } from "svelte";
|
5 |
+
|
6 |
+
export let value;
|
7 |
+
|
8 |
+
$: plot = value?.plot;
|
9 |
+
|
10 |
+
let plot_div;
|
11 |
+
let plotly_global_style;
|
12 |
+
|
13 |
+
const dispatch = createEventDispatcher<{ load: undefined }>();
|
14 |
+
|
15 |
+
function load_plotly_css(): void {
|
16 |
+
if (!plotly_global_style) {
|
17 |
+
plotly_global_style = document.getElementById("plotly.js-style-global");
|
18 |
+
const plotly_style_clone = plotly_global_style.cloneNode();
|
19 |
+
plot_div.appendChild(plotly_style_clone);
|
20 |
+
for (const rule of plotly_global_style.sheet.cssRules) {
|
21 |
+
plotly_style_clone.sheet.insertRule(rule.cssText);
|
22 |
+
}
|
23 |
+
}
|
24 |
+
}
|
25 |
+
|
26 |
+
afterUpdate(async () => {
|
27 |
+
load_plotly_css();
|
28 |
+
|
29 |
+
let plotObj = JSON.parse(plot);
|
30 |
+
|
31 |
+
// the docs aren't very good but this works
|
32 |
+
plotObj.config = plotObj.config || {};
|
33 |
+
plotObj.config.responsive = true;
|
34 |
+
plotObj.responsive = true;
|
35 |
+
plotObj.layout.autosize = true;
|
36 |
+
|
37 |
+
plotObj.layout.title
|
38 |
+
? (plotObj.layout.margin = { autoexpand: true })
|
39 |
+
: (plotObj.layout.margin = { l: 0, r: 0, b: 0, t: 0 });
|
40 |
+
|
41 |
+
Plotly.react(plot_div, plotObj.data, plotObj.layout, plotObj.config);
|
42 |
+
Plotly.Plots.resize(plot_div);
|
43 |
+
|
44 |
+
plot_div.on("plotly_afterplot", () => {
|
45 |
+
dispatch("load");
|
46 |
+
});
|
47 |
+
});
|
48 |
+
</script>
|
49 |
+
|
50 |
+
<div data-testid={"plotly"} bind:this={plot_div} />
|
src/frontend/shared/plot_types/altair_utils.ts
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { colors as color_palette } from "@gradio/theme";
|
2 |
+
import { get_next_color } from "@gradio/utils";
|
3 |
+
import type { Config, TopLevelSpec as Spec } from "vega-lite";
|
4 |
+
|
5 |
+
export function set_config(
|
6 |
+
spec: Spec,
|
7 |
+
computed_style: CSSStyleDeclaration,
|
8 |
+
chart_type: string,
|
9 |
+
colors: string[]
|
10 |
+
): Spec {
|
11 |
+
let accentColor = computed_style.getPropertyValue("--color-accent");
|
12 |
+
let bodyTextColor = computed_style.getPropertyValue("--body-text-color");
|
13 |
+
let borderColorPrimary = computed_style.getPropertyValue(
|
14 |
+
"--border-color-primary"
|
15 |
+
);
|
16 |
+
let fontFamily = computed_style.fontFamily;
|
17 |
+
let titleWeight = computed_style.getPropertyValue(
|
18 |
+
"--block-title-text-weight"
|
19 |
+
) as "bold" | "normal" | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
|
20 |
+
const fontToPxVal = (font: string): number => {
|
21 |
+
return font.endsWith("px") ? parseFloat(font.slice(0, -2)) : 12;
|
22 |
+
};
|
23 |
+
let textSizeMd = fontToPxVal(computed_style.getPropertyValue("--text-md"));
|
24 |
+
let textSizeSm = fontToPxVal(computed_style.getPropertyValue("--text-sm"));
|
25 |
+
let config: Config = {
|
26 |
+
autosize: { type: "fit", contains: "padding" },
|
27 |
+
axis: {
|
28 |
+
labelFont: fontFamily,
|
29 |
+
labelColor: bodyTextColor,
|
30 |
+
titleFont: fontFamily,
|
31 |
+
titleColor: bodyTextColor,
|
32 |
+
tickColor: borderColorPrimary,
|
33 |
+
labelFontSize: textSizeSm,
|
34 |
+
gridColor: borderColorPrimary,
|
35 |
+
titleFontWeight: "normal",
|
36 |
+
titleFontSize: textSizeSm,
|
37 |
+
labelFontWeight: "normal",
|
38 |
+
domain: false,
|
39 |
+
labelAngle: 0
|
40 |
+
},
|
41 |
+
legend: {
|
42 |
+
labelColor: bodyTextColor,
|
43 |
+
labelFont: fontFamily,
|
44 |
+
titleColor: bodyTextColor,
|
45 |
+
titleFont: fontFamily,
|
46 |
+
titleFontWeight: "normal",
|
47 |
+
titleFontSize: textSizeSm,
|
48 |
+
labelFontWeight: "normal",
|
49 |
+
offset: 2
|
50 |
+
},
|
51 |
+
title: {
|
52 |
+
color: bodyTextColor,
|
53 |
+
font: fontFamily,
|
54 |
+
fontSize: textSizeMd,
|
55 |
+
fontWeight: titleWeight,
|
56 |
+
anchor: "middle"
|
57 |
+
},
|
58 |
+
view: {
|
59 |
+
stroke: borderColorPrimary
|
60 |
+
}
|
61 |
+
};
|
62 |
+
spec.config = config;
|
63 |
+
// @ts-ignore (unsure why the following are not typed in Spec)
|
64 |
+
let encoding: any = spec.encoding;
|
65 |
+
// @ts-ignore
|
66 |
+
let layer: any = spec.layer;
|
67 |
+
switch (chart_type) {
|
68 |
+
case "scatter":
|
69 |
+
spec.config.mark = { stroke: accentColor };
|
70 |
+
if (encoding.color && encoding.color.type == "nominal") {
|
71 |
+
encoding.color.scale.range = encoding.color.scale.range.map(
|
72 |
+
(_: string, i: number) => get_color(colors, i)
|
73 |
+
);
|
74 |
+
} else if (encoding.color && encoding.color.type == "quantitative") {
|
75 |
+
encoding.color.scale.range = ["#eff6ff", "#1e3a8a"];
|
76 |
+
encoding.color.scale.range.interpolate = "hsl";
|
77 |
+
}
|
78 |
+
break;
|
79 |
+
case "line":
|
80 |
+
spec.config.mark = { stroke: accentColor, cursor: "crosshair" };
|
81 |
+
layer.forEach((d: any) => {
|
82 |
+
if (d.encoding.color) {
|
83 |
+
d.encoding.color.scale.range = d.encoding.color.scale.range.map(
|
84 |
+
(_: any, i: any) => get_color(colors, i)
|
85 |
+
);
|
86 |
+
}
|
87 |
+
});
|
88 |
+
break;
|
89 |
+
case "bar":
|
90 |
+
spec.config.mark = { opacity: 0.8, fill: accentColor };
|
91 |
+
if (encoding.color) {
|
92 |
+
encoding.color.scale.range = encoding.color.scale.range.map(
|
93 |
+
(_: any, i: any) => get_color(colors, i)
|
94 |
+
);
|
95 |
+
}
|
96 |
+
break;
|
97 |
+
}
|
98 |
+
return spec;
|
99 |
+
}
|
100 |
+
|
101 |
+
function get_color(colors: string[], index: number): string {
|
102 |
+
let current_color = colors[index % colors.length];
|
103 |
+
|
104 |
+
if (current_color && current_color in color_palette) {
|
105 |
+
return color_palette[current_color as keyof typeof color_palette]?.primary;
|
106 |
+
} else if (!current_color) {
|
107 |
+
return color_palette[get_next_color(index) as keyof typeof color_palette]
|
108 |
+
.primary;
|
109 |
+
}
|
110 |
+
return current_color;
|
111 |
+
}
|
src/frontend/testplot.js
ADDED
The diff for this file is too large to render.
See raw diff
|
|
src/pyproject.toml
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[build-system]
|
2 |
+
requires = [
|
3 |
+
"hatchling",
|
4 |
+
"hatch-requirements-txt",
|
5 |
+
"hatch-fancy-pypi-readme>=22.5.0",
|
6 |
+
]
|
7 |
+
build-backend = "hatchling.build"
|
8 |
+
|
9 |
+
[project]
|
10 |
+
name = "gradio_msaplot"
|
11 |
+
version = "0.0.1"
|
12 |
+
description = "MSAplot is customizable panels for plotting MSA, seqlogo, annotation, and consensus histograms."
|
13 |
+
readme = "README.md"
|
14 |
+
license = "mit"
|
15 |
+
requires-python = ">=3.6"
|
16 |
+
authors = [{ name = "Joseph Pollack", email = "[email protected]" }]
|
17 |
+
keywords = ["gradio-custom-component", "gradio-template-Plot", "med", "medicine", "bio", "biology", "chem", "chemistry", "MSA", "multiple sequence alignment", "seqlogo", "annotation", "consensus histogram", "visualize"]
|
18 |
+
# Add dependencies here
|
19 |
+
dependencies = [
|
20 |
+
"gradio>=4.0,<6.0",
|
21 |
+
"seaborn>=0.9",
|
22 |
+
"matplotlib>=2.2.2"
|
23 |
+
]
|
24 |
+
classifiers = [
|
25 |
+
'Development Status :: 3 - Alpha',
|
26 |
+
'Operating System :: OS Independent',
|
27 |
+
'Programming Language :: Python :: 3',
|
28 |
+
'Programming Language :: Python :: 3 :: Only',
|
29 |
+
'Programming Language :: Python :: 3.8',
|
30 |
+
'Programming Language :: Python :: 3.9',
|
31 |
+
'Programming Language :: Python :: 3.10',
|
32 |
+
'Programming Language :: Python :: 3.11',
|
33 |
+
'Topic :: Scientific/Engineering',
|
34 |
+
'Topic :: Scientific/Engineering :: Artificial Intelligence',
|
35 |
+
'Topic :: Scientific/Engineering :: Visualization',
|
36 |
+
]
|
37 |
+
|
38 |
+
# The repository and space URLs are optional, but recommended.
|
39 |
+
# Adding a repository URL will create a badge in the auto-generated README that links to the repository.
|
40 |
+
# Adding a space URL will create a badge in the auto-generated README that links to the space.
|
41 |
+
# This will make it easy for people to find your deployed demo or source code when they
|
42 |
+
# encounter your project in the wild.
|
43 |
+
|
44 |
+
[project.urls]
|
45 |
+
repository = "https://github.com/Josephrp/GradioMSAPlot.git"
|
46 |
+
space = "https://huggingface.co/spaces/seq-to-pheno/MSAPlot"
|
47 |
+
|
48 |
+
[project.optional-dependencies]
|
49 |
+
dev = ["build", "twine"]
|
50 |
+
|
51 |
+
[tool.hatch.build]
|
52 |
+
artifacts = ["/backend/gradio_msaplot/templates", "*.pyi"]
|
53 |
+
|
54 |
+
[tool.hatch.build.targets.wheel]
|
55 |
+
packages = ["/backend/gradio_msaplot"]
|