抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

note Reflex 是一个开源库,旨在使用纯 Python 构建全栈 Web 应用程序。它的设计理念是让开发者能够完全使用 Python 编写应用的前端和后端,无需学习 JavaScript。这对于那些希望专注于 Python 编程语言的开发者来说是一个重大的福音。

上面说的都是场面话,这都依托于它自身包装的第三方应用够丰富。但是,对于一些冷门的应用,比如我这里要说的IGV浏览器,就没有那么容易了。这里我就来分享一下我是如何在 Reflex 里面使用 IGV 浏览器的。

包装igv.js到react组件

首先,我们需要把 igv.js 包装成一个 React 组件。我们以两个需要实现的交互为例子来说明:

  • 第一个是我们有一个外部的控制器,可以通过这个控制器来控制 igv.js 显示的坐标范围。
  • 第二个是当我们鼠标点击某个区域的时候,我们可以获取到这个区域的所有信息。

首先,通过查阅 IGV.js的文档,我们知道,我们可以通过 igv.createBrowser 来创建一个 IGV 浏览器。我们可以通过这个函数来创建一个 IGV 浏览器,然后通过这个浏览器的实例来控制 IGV 的显示。

import igv from "https://unpkg.com/igv@3.0.0/dist/igv.esm.min.js"

const div = document.getElementById("igv_div")

const config = {
genome: "hg19"
}

const browser = await igv.createBrowser(div, config)

然后我们可以通过igv.removeAllBrowsers()来移除所有的浏览器。

那么,我们放到 React 组件里面,就是这样的:

import React, { useEffect, useRef } from "react";
import igv from "igv";

const IgvComponent = () => {
const igvContainerRef = useRef(null);
const browserRef = useRef(null);
useEffect(()=>{
const options = {
genome: 'hg19',
};
igv.createBrowser(igvContainerRef.current, options).then(browser => {
browserRef.current = browser;
});
});

return (
<>
<div ref={igvContainerRef} />;
</>
);
};

export default IgvComponent;

现在,我们来添加交互,我们想要实现的第二个功能需要 browser.on来监听事件。比如我们想要监听鼠标点击事件,我们可以这样:

browser.on("trackclick", (track, popoverData) => {})

我们放入 React 组件里面,就是这样的:

import React, { useEffect, useRef } from "react";
import igv from "igv";

const IgvComponent = (genome, locus, tracks, onTrackClick) => {
const igvContainerRef = useRef(null);
const browserRef = useRef(null);
useEffect(()=>{
const options = {
genome,
locus,
tracks,
};
igv.createBrowser(igvContainerRef.current, options).then(browser => {
browserRef.current = browser;
if (onTrackClick) {
browser.on("trackclick", (track, popoverData) => {
const trackName = track.name;
const trackValue = popoverData.map((data) => ({
name: data.name,
value: data.value,
}));
onTrackClick(trackName, trackValue);
});
}
});
});

return (
<>
<div ref={igvContainerRef} />;
</>
);
};

export default IgvComponent;

而第一个要求里,我们要外部控制,需要获取到browser的实例,然后通过这个实例来控制 IGV 的显示。我们可以通过 browser.search 来搜索某个基因,通过 browser.zoomInbrowser.zoomOut 来缩放。这在react里是这样的:

import React, { useEffect, useRef } from "react";
import igv from "../node_modules/igv/dist/igv.esm.min.js";

const IgvComponent = ({ genome, locus, tracks, onTrackClick }) => {
const igvContainerRef = useRef(null);
const browserRef = useRef(null);

useEffect(() => {
const options = {
genome,
locus,
tracks,
};

if (!browserRef.current) {
igv.createBrowser(igvContainerRef.current, options).then((browser) => {
browserRef.current = browser;
if (onTrackClick) {
browser.on("trackclick", (track, popoverData) => {
const trackName = track.name;
const trackValue = popoverData.map((data) => ({
name: data.name,
value: data.value,
}));
onTrackClick(trackName, trackValue);
});
}
});
}

// 清理函数,在组件卸载时调用
return () => {
if (browserRef.current) {
igv.removeAllBrowsers()
}
};
}, []);

useEffect(() => {
if (browserRef.current) {
browserRef.current.search(locus);
}
}, [locus]);

return <div ref={igvContainerRef} />;
};

export default IgvComponent;

这样,我们就完成了最终的包装。我们可以自己搭建React环境来测试一下是否可行,或者可以去找在线平台测试,例如playcode

在 Reflex 里面使用

根据官方文档的表述,React组件的包装就相当简单了,我们这里是本地的组件,所以就可以这样:

### IGV.py
import reflex as rx
from typing import List, Dict
from reflex.components.component import NoSSRComponent

### 一定要注意这个只可以在客户端渲染,不支持服务端
class IGV(NoSSRComponent):
###官方文档告诉我们,我们的组件需要放在assets文件夹下,但是这里要写public
library = "/public/igv"

tag = "IgvComponent"

is_default = True

lib_dependencies: list[str] = [
"igv@3.0.6"
]

genome: rx.Var[str]
locus: rx.Var[str]
tracks: rx.Var[List[Dict[str, str]]]

on_track_click: rx.EventHandler[lambda e0, e1: [e0, e1]]


igvComponent = IGV.create

一定要注意的是,Reflex默认是服务端渲染的,但是显然 IGV浏览器无法在服务端兼容,那么我们就是用 NoSSRComponent来包装。

如何使用?

我们可以写一个小的 Demo来测试一下:

import reflex as rx

from .IGV import igvComponent


class State(rx.State):
"""The app state."""
genome: str = "hg38"
loc: str = "chr8:127736588-127739371"
tracks: list[dict[str, str]] = [
{
"name": "HG00103",
"url": "https://s3.amazonaws.com/1000genomes/data/HG00103/alignment/HG00103.alt_bwamem_GRCh38DH.20150718.GBR.low_coverage.cram",
"indexURL": "https://s3.amazonaws.com/1000genomes/data/HG00103/alignment/HG00103.alt_bwamem_GRCh38DH.20150718.GBR.low_coverage.cram.crai",
"format": "cram"
}]
location: str
trackName: str
trackdata: list[dict[str, str]]

def change_loc(self):
self.loc = self.location

def get_click_info(self, e0: str, e1: list[dict[str, str]]):
self.trackName = e0
self.trackdata = [{"name": itm['name'], "value": itm['value']}
for itm in e1 if itm['name']]


def data_item(name: str, value: str) -> rx.Component:
return rx.data_list.item(
rx.data_list.label(name),
rx.data_list.value(value)
)


def data_card(trackdata: list[dict[str, str]]) -> rx.Component:
return rx.card(
rx.data_list.root(
rx.foreach(trackdata, lambda item: data_item(
item['name'], item['value']))
)
)


def index() -> rx.Component:
# Welcome Page (Index)
return rx.container(
rx.vstack(
rx.heading("IGV test!", size="9"),
rx.box(
igvComponent(genome=State.genome, locus=State.loc,
tracks=State.tracks, on_track_click=State.get_click_info),
width="100%",
),
rx.hstack(
rx.input(placeholder="Enter location",
on_blur=State.set_location,
width="40%"),
rx.button("GoTo!", on_click=State.change_loc),
width="100%",
justify="center"
),
rx.divider(),
rx.cond(
State.trackName,
rx.vstack(
rx.heading(State.trackName, size="4"),
data_card(State.trackdata)
),
rx.heading("No track clicked", size="4")
),

spacing="5",
justify="center",
min_height="85vh",
),
rx.logo()
)


app = rx.App()
app.add_page(index)

使用就很简单了,一旦包装好,我们导入它就可以当做官方的组件来使用了,我们可以为他写 State,写 EventHandler,写页面,写交互,写样式,写逻辑,写一切。

总结

这里我们介绍了如何将 igv.js 包装成 React 组件,然后将 React 组件包装成 Reflex 组件,最后在 Reflex 里面使用。这里只是一个简单的例子,实陿上,我们可以包装任何我们想要的组件,只要我们有足够的耐心和技术。本文的代码在 Github 可以获取。

note 这里的 igv.js 是一个简单的例子,实陿上,我们可以包装任何我们想要的组件,只要我们有足够的耐心和技术。

评论