Tonic commited on
Commit
7873945
1 Parent(s): ae330b9

Upload folder using huggingface_hub

Browse files
.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
- title: Gradio Msaplot
3
- emoji: 🦀
4
- colorFrom: gray
5
- colorTo: pink
6
- sdk: gradio
7
- sdk_version: 5.1.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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"]