Skip to content

Commit 3e8a137

Browse files
tally edits
1 parent 9e8d81d commit 3e8a137

File tree

1 file changed

+116
-17
lines changed

1 file changed

+116
-17
lines changed

scripts/tallyModalities.py

Lines changed: 116 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,51 +17,63 @@
1717
"rnaseq": "Bulk-Transcriptomics",
1818
"rna seq": "Bulk-Transcriptomics",
1919
"rna seq.": "Bulk-Transcriptomics",
20-
2120
# Single-cell gene expression
2221
"sc gex": "scRNA-Seq",
2322
"single cell rna-seq": "scRNA-Seq",
2423
"scrna-seq": "scRNA-Seq",
2524
"sc rna-seq": "scRNA-Seq",
26-
2725
# ATAC-seq
2826
"bulk atacseq": "Bulk-Epigenetics",
2927
"bulk atac-seq": "Bulk-Epigenetics",
3028
"sc atac": "scATAC-Seq",
3129
"sc atac-seq": "scATAC-Seq",
32-
3330
# Multiome and others
3431
"10x multiome": "10x Multiome",
3532
"resolveome": "ResolveOME",
36-
3733
# Spatial platforms
3834
"spatial": "Spatial",
3935
"visium": "10x Visium",
4036
"10x visium": "10x Visium",
4137
"xenium": "10x Xenium",
4238
"xeniums": "10x Xenium",
4339
"10x xenium": "10x Xenium",
44-
4540
# Misc
4641
"mgx": "Metagenomics",
4742
"workflow dev": "Workflow development",
48-
"workflow development" : "Workflow development",
49-
"workflows" : "Workflow development",
50-
"development" : "Workflow development",
43+
"workflow development": "Workflow development",
44+
"workflows": "Workflow development",
45+
"development": "Workflow development",
5146
"other": "Other",
5247
}
5348

49+
# Color palette for modalities (GitHub-style colors)
50+
COLORS: Dict[str, str] = {
51+
"Bulk-Transcriptomics": "#e34c26",
52+
"scRNA-Seq": "#3572A5",
53+
"Bulk-Epigenetics": "#178600",
54+
"scATAC-Seq": "#89e051",
55+
"10x Multiome": "#f1e05a",
56+
"ResolveOME": "#b07219",
57+
"Spatial": "#555555",
58+
"10x Visium": "#4F5D95",
59+
"10x Xenium": "#DA5B0B",
60+
"Metagenomics": "#701516",
61+
"Workflow development": "#384d54",
62+
"Other": "#cccccc",
63+
}
64+
65+
5466
def normalize(modality: str) -> str:
5567
m = modality.strip()
5668
if not m:
5769
return ""
5870
key = m.lower()
5971
return ALIASES.get(key, m)
6072

73+
6174
def extract_modalities_from_markdown(md_text: str) -> List[str]:
6275
modalities: List[str] = []
6376
in_counts_section = False
64-
6577
for line in md_text.splitlines():
6678
# Skip the generated counts section entirely
6779
if START in line:
@@ -72,23 +84,19 @@ def extract_modalities_from_markdown(md_text: str) -> List[str]:
7284
continue
7385
if in_counts_section:
7486
continue
75-
7687
if not line.startswith("|"):
7788
continue
7889
if re.match(r"^\|\s*Project\s*\|", line):
7990
continue
8091
if re.match(r"^\|\s*-+\s*\|", line):
8192
continue
82-
8393
cells = [c.strip() for c in line.split("|")]
8494
# Expect at least 4 cells: leading empty, Project, Modality, Repo/Count, trailing empty
8595
if len(cells) < 4:
8696
continue
87-
8897
modality_cell = cells[2] # Modality is the second visible column
8998
if not modality_cell:
9099
continue
91-
92100
parts = re.split(r"\s*,\s*", modality_cell)
93101
for p in parts:
94102
n = normalize(p)
@@ -97,7 +105,84 @@ def extract_modalities_from_markdown(md_text: str) -> List[str]:
97105
return modalities
98106

99107

108+
def get_color(modality: str) -> str:
109+
"""Get color for a modality, or generate a default one."""
110+
return COLORS.get(modality, "#cccccc")
111+
112+
113+
def build_badge_svg(counts: Counter) -> str:
114+
"""Build an SVG badge similar to GitHub's language bar."""
115+
if not counts:
116+
return ""
117+
118+
total = sum(counts.values())
119+
rows: List[Tuple[str, int]] = sorted(
120+
counts.items(), key=lambda x: (-x[1], x[0].lower())
121+
)
122+
123+
# SVG dimensions
124+
width = 600
125+
bar_height = 8
126+
legend_item_height = 25
127+
legend_height = len(rows) * legend_item_height
128+
total_height = bar_height + 20 + legend_height
129+
130+
# Build bar segments
131+
x_pos = 0
132+
bar_rects = []
133+
for modality, count in rows:
134+
percentage = (count / total) * 100
135+
segment_width = (count / total) * width
136+
color = get_color(modality)
137+
bar_rects.append(
138+
f'<rect x="{x_pos:.2f}" y="0" width="{segment_width:.2f}" height="{bar_height}" '
139+
f'fill="{color}"><title>{modality}: {count} ({percentage:.1f}%)</title></rect>'
140+
)
141+
x_pos += segment_width
142+
143+
# Build legend
144+
legend_items = []
145+
y_pos = bar_height + 20
146+
for modality, count in rows:
147+
percentage = (count / total) * 100
148+
color = get_color(modality)
149+
legend_items.append(
150+
f'<circle cx="6" cy="{y_pos}" r="5" fill="{color}"/>'
151+
)
152+
legend_items.append(
153+
f'<text x="18" y="{y_pos + 4}" class="legend-text">{modality}</text>'
154+
)
155+
legend_items.append(
156+
f'<text x="{width - 10}" y="{y_pos + 4}" class="legend-percent" text-anchor="end">'
157+
f'{percentage:.1f}% ({count})</text>'
158+
)
159+
y_pos += legend_item_height
160+
161+
svg = f'''<svg width="{width}" height="{total_height}" xmlns="http://www.w3.org/2000/svg">
162+
<style>
163+
.legend-text {{
164+
font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
165+
fill: #24292f;
166+
}}
167+
.legend-percent {{
168+
font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
169+
fill: #656d76;
170+
font-weight: 600;
171+
}}
172+
</style>
173+
<g id="bar">
174+
{chr(10).join(f" {rect}" for rect in bar_rects)}
175+
</g>
176+
<g id="legend">
177+
{chr(10).join(f" {item}" for item in legend_items)}
178+
</g>
179+
</svg>'''
180+
181+
return f'<p align="center">\n{svg}\n</p>'
182+
183+
100184
def build_table(counts: Counter) -> str:
185+
"""Build a simple markdown table (fallback)."""
101186
rows: List[Tuple[str, int]] = sorted(
102187
counts.items(), key=lambda x: (-x[1], x[0].lower())
103188
)
@@ -109,12 +194,18 @@ def build_table(counts: Counter) -> str:
109194
lines.append(f"| {modality} | {cnt} |")
110195
return "\n".join(lines)
111196

112-
def upsert_section(md_text: str, table_md: str) -> str:
197+
198+
def upsert_section(md_text: str, badge_svg: str, table_md: str) -> str:
199+
"""Insert both badge and table into README."""
113200
section = (
114201
f"{START}\n"
115202
f"\n"
116-
f"### Modality counts\n\n"
117-
f"{table_md}\n"
203+
f"### Modality Distribution\n\n"
204+
f"{badge_svg}\n\n"
205+
f"<details>\n"
206+
f"<summary>View as table</summary>\n\n"
207+
f"{table_md}\n\n"
208+
f"</details>\n"
118209
f"\n"
119210
f"{END}"
120211
)
@@ -128,21 +219,29 @@ def upsert_section(md_text: str, table_md: str) -> str:
128219
sep = "\n\n" if not md_text.endswith("\n") else "\n"
129220
return md_text + sep + section + "\n"
130221

222+
131223
def main() -> int:
132224
if not README.exists():
133225
print("README.md not found at repo root.")
134226
return 1
227+
135228
md_text = README.read_text(encoding="utf-8")
136229
modalities = extract_modalities_from_markdown(md_text)
137230
counts = Counter(modalities)
231+
232+
badge_svg = build_badge_svg(counts)
138233
table_md = build_table(counts)
139-
new_md = upsert_section(md_text, table_md)
234+
235+
new_md = upsert_section(md_text, badge_svg, table_md)
236+
140237
if new_md != md_text:
141238
README.write_text(new_md, encoding="utf-8")
142239
print("README.md updated with modality counts.")
143240
else:
144241
print("No changes to README.md.")
242+
145243
return 0
146244

245+
147246
if __name__ == "__main__":
148247
raise SystemExit(main())

0 commit comments

Comments
 (0)