Add boxplot
This commit is contained in:
parent
37692d5775
commit
1514130adc
@ -94,5 +94,49 @@ def get_cell_types():
|
||||
]
|
||||
return jsonify({"stage": stage, "cells": result})
|
||||
|
||||
@app.route("/api/gene_dist")
|
||||
def get_gene_distribution():
|
||||
gene = request.args.get("gene")
|
||||
stage = request.args.get("stage")
|
||||
adata = load_adata(stage)
|
||||
|
||||
if not adata:
|
||||
return jsonify({"error": f"Stage '{stage}' not found"}), 404
|
||||
if gene not in adata.var_names:
|
||||
return jsonify({"error": f"Gene '{gene}' not found in {stage}"}), 404
|
||||
|
||||
# 查找细胞类型列,按常见的列名优先级查找
|
||||
cell_type_columns = ['cell_type', 'celltype', 'cluster', 'annotation', 'cell_types', 'clusters']
|
||||
cell_type_col = None
|
||||
|
||||
for col in cell_type_columns:
|
||||
if col in adata.obs.columns:
|
||||
cell_type_col = col
|
||||
break
|
||||
|
||||
if cell_type_col is None:
|
||||
return jsonify({"error": "No cell type information found"}), 404
|
||||
|
||||
# 读取基因表达值
|
||||
expr = adata[:, gene].X
|
||||
expr = expr.toarray().flatten() if hasattr(expr, "toarray") else expr.flatten()
|
||||
|
||||
# 读取细胞类型
|
||||
cell_types = adata.obs[cell_type_col].values
|
||||
|
||||
# 按细胞类型分组表达值
|
||||
distribution = {}
|
||||
for cell_type, expression in zip(cell_types, expr):
|
||||
cell_type_str = str(cell_type)
|
||||
if cell_type_str not in distribution:
|
||||
distribution[cell_type_str] = []
|
||||
distribution[cell_type_str].append(float(expression))
|
||||
|
||||
return jsonify({
|
||||
"gene": gene,
|
||||
"stage": stage,
|
||||
"distribution": distribution
|
||||
})
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True)
|
||||
|
||||
479
embryo-frontend/package-lock.json
generated
479
embryo-frontend/package-lock.json
generated
@ -8,6 +8,7 @@
|
||||
"name": "embryo-frontend",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@nivo/boxplot": "^0.99.0",
|
||||
"axios": "^1.10.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"react": "^19.1.0",
|
||||
@ -479,6 +480,268 @@
|
||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@nivo/annotations": {
|
||||
"version": "0.99.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/annotations/-/annotations-0.99.0.tgz",
|
||||
"integrity": "sha512-jCuuXPbvpaqaz4xF7k5dv0OT2ubn5Nt0gWryuTe/8oVsC/9bzSuK8bM9vBty60m9tfO+X8vUYliuaCDwGksC2g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nivo/colors": "0.99.0",
|
||||
"@nivo/core": "0.99.0",
|
||||
"@nivo/theming": "0.99.0",
|
||||
"@react-spring/web": "9.4.5 || ^9.7.2 || ^10.0",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14 || ^17.0 || ^18.0 || ^19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nivo/axes": {
|
||||
"version": "0.99.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/axes/-/axes-0.99.0.tgz",
|
||||
"integrity": "sha512-3KschnmEL0acRoa7INSSOSEFwJLm54aZwSev7/r8XxXlkgRBriu6ReZy/FG0wfN+ljZ4GMvx+XyIIf6kxzvrZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nivo/core": "0.99.0",
|
||||
"@nivo/scales": "0.99.0",
|
||||
"@nivo/text": "0.99.0",
|
||||
"@nivo/theming": "0.99.0",
|
||||
"@react-spring/web": "9.4.5 || ^9.7.2 || ^10.0",
|
||||
"@types/d3-format": "^1.4.1",
|
||||
"@types/d3-time-format": "^2.3.1",
|
||||
"d3-format": "^1.4.4",
|
||||
"d3-time-format": "^3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14 || ^17.0 || ^18.0 || ^19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nivo/boxplot": {
|
||||
"version": "0.99.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/boxplot/-/boxplot-0.99.0.tgz",
|
||||
"integrity": "sha512-1XD5ILCBbmA8ghtUBg0gMaXNQGJgky0yLrcjGCfq6yqNLwscsn2rSKKbCtH9HMUTRrkb0iqkhoAzbX1cvsTe+Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nivo/annotations": "0.99.0",
|
||||
"@nivo/axes": "0.99.0",
|
||||
"@nivo/colors": "0.99.0",
|
||||
"@nivo/core": "0.99.0",
|
||||
"@nivo/legends": "0.99.0",
|
||||
"@nivo/scales": "0.99.0",
|
||||
"@nivo/theming": "0.99.0",
|
||||
"@nivo/tooltip": "0.99.0",
|
||||
"@react-spring/web": "9.4.5 || ^9.7.2 || ^10.0",
|
||||
"@types/d3-scale": "^4.0.8",
|
||||
"@types/d3-shape": "^3.1.6",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-shape": "^3.2.0",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14 || ^17.0 || ^18.0 || ^19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nivo/colors": {
|
||||
"version": "0.99.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/colors/-/colors-0.99.0.tgz",
|
||||
"integrity": "sha512-hyYt4lEFIfXOUmQ6k3HXm3KwhcgoJpocmoGzLUqzk7DzuhQYJo+4d5jIGGU0N/a70+9XbHIdpKNSblHAIASD3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nivo/core": "0.99.0",
|
||||
"@nivo/theming": "0.99.0",
|
||||
"@types/d3-color": "^3.0.0",
|
||||
"@types/d3-scale": "^4.0.8",
|
||||
"@types/d3-scale-chromatic": "^3.0.0",
|
||||
"d3-color": "^3.1.0",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-scale-chromatic": "^3.0.0",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14 || ^17.0 || ^18.0 || ^19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nivo/core": {
|
||||
"version": "0.99.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.99.0.tgz",
|
||||
"integrity": "sha512-olCItqhPG3xHL5ei+vg52aB6o+6S+xR2idpkd9RormTTUniZb8U2rOdcQojOojPY5i9kVeQyLFBpV4YfM7OZ9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nivo/theming": "0.99.0",
|
||||
"@nivo/tooltip": "0.99.0",
|
||||
"@react-spring/web": "9.4.5 || ^9.7.2 || ^10.0",
|
||||
"@types/d3-shape": "^3.1.6",
|
||||
"d3-color": "^3.1.0",
|
||||
"d3-format": "^1.4.4",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-scale-chromatic": "^3.0.0",
|
||||
"d3-shape": "^3.2.0",
|
||||
"d3-time-format": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"react-virtualized-auto-sizer": "^1.0.26",
|
||||
"use-debounce": "^10.0.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/nivo/donate"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14 || ^17.0 || ^18.0 || ^19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nivo/legends": {
|
||||
"version": "0.99.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.99.0.tgz",
|
||||
"integrity": "sha512-P16FjFqNceuTTZphINAh5p0RF0opu3cCKoWppe2aRD9IuVkvRm/wS5K1YwMCxDzKyKh5v0AuTlu9K6o3/hk8hA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nivo/colors": "0.99.0",
|
||||
"@nivo/core": "0.99.0",
|
||||
"@nivo/text": "0.99.0",
|
||||
"@nivo/theming": "0.99.0",
|
||||
"@types/d3-scale": "^4.0.8",
|
||||
"d3-scale": "^4.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14 || ^17.0 || ^18.0 || ^19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nivo/scales": {
|
||||
"version": "0.99.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/scales/-/scales-0.99.0.tgz",
|
||||
"integrity": "sha512-g/2K4L6L8si6E2BWAHtFVGahtDKbUcO6xHJtlIZMwdzaJc7yB16EpWLK8AfI/A42KadLhJSJqBK3mty+c7YZ+w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-interpolate": "^3.0.4",
|
||||
"@types/d3-scale": "^4.0.8",
|
||||
"@types/d3-time": "^1.1.1",
|
||||
"@types/d3-time-format": "^3.0.0",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-time": "^1.0.11",
|
||||
"d3-time-format": "^3.0.0",
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@nivo/scales/node_modules/@types/d3-time-format": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-3.0.4.tgz",
|
||||
"integrity": "sha512-or9DiDnYI1h38J9hxKEsw513+KVuFbEVhl7qdxcaudoiqWWepapUen+2vAriFGexr6W5+P4l9+HJrB39GG+oRg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@nivo/text": {
|
||||
"version": "0.99.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/text/-/text-0.99.0.tgz",
|
||||
"integrity": "sha512-ho3oZpAZApsJNjsIL5WJSAdg/wjzTBcwo1KiHBlRGUmD+yUWO8qp7V+mnYRhJchwygtRVALlPgZ/rlcW2Xr/MQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nivo/core": "0.99.0",
|
||||
"@nivo/theming": "0.99.0",
|
||||
"@react-spring/web": "9.4.5 || ^9.7.2 || ^10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14 || ^17.0 || ^18.0 || ^19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nivo/theming": {
|
||||
"version": "0.99.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/theming/-/theming-0.99.0.tgz",
|
||||
"integrity": "sha512-KvXlf0nqBzh/g2hAIV9bzscYvpq1uuO3TnFN3RDXGI72CrbbZFTGzprPju3sy/myVsauv+Bb+V4f5TZ0jkYKRg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14 || ^17.0 || ^18.0 || ^19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nivo/tooltip": {
|
||||
"version": "0.99.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/tooltip/-/tooltip-0.99.0.tgz",
|
||||
"integrity": "sha512-weoEGR3xAetV4k2P6k96cdamGzKQ5F2Pq+uyDaHr1P3HYArM879Pl+x+TkU0aWjP6wgUZPx/GOBiV1Hb1JxIqg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nivo/core": "0.99.0",
|
||||
"@nivo/theming": "0.99.0",
|
||||
"@react-spring/web": "9.4.5 || ^9.7.2 || ^10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14 || ^17.0 || ^18.0 || ^19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-spring/animated": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-10.0.1.tgz",
|
||||
"integrity": "sha512-BGL3hA66Y8Qm3KmRZUlfG/mFbDPYajgil2/jOP0VXf2+o2WPVmcDps/eEgdDqgf5Pv9eBbyj7LschLMuSjlW3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-spring/shared": "~10.0.1",
|
||||
"@react-spring/types": "~10.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-spring/core": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/core/-/core-10.0.1.tgz",
|
||||
"integrity": "sha512-KaMMsN1qHuVTsFpg/5ajAVye7OEqhYbCq0g4aKM9bnSZlDBBYpO7Uf+9eixyXN8YEbF+YXaYj9eoWDs+npZ+sA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-spring/animated": "~10.0.1",
|
||||
"@react-spring/shared": "~10.0.1",
|
||||
"@react-spring/types": "~10.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/react-spring/donate"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-spring/rafz": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-10.0.1.tgz",
|
||||
"integrity": "sha512-UrzG/d6Is+9i0aCAjsjWRqIlFFiC4lFqFHrH63zK935z2YDU95TOFio4VKGISJ5SG0xq4ULy7c1V3KU+XvL+Yg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-spring/shared": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-10.0.1.tgz",
|
||||
"integrity": "sha512-KR2tmjDShPruI/GGPfAZOOLvDgkhFseabjvxzZFFggJMPkyICLjO0J6mCIoGtdJSuHywZyc4Mmlgi+C88lS00g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-spring/rafz": "~10.0.1",
|
||||
"@react-spring/types": "~10.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-spring/types": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/types/-/types-10.0.1.tgz",
|
||||
"integrity": "sha512-Fk1wYVAKL+ZTYK+4YFDpHf3Slsy59pfFFvnnTfRjQQFGlyIo4VejPtDs3CbDiuBjM135YztRyZjIH2VbycB+ZQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-spring/web": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/web/-/web-10.0.1.tgz",
|
||||
"integrity": "sha512-FgQk02OqFrYyJBTTnBTWAU0WPzkHkKXauc6aeexcvATvLapUxwnfGuLlsLYF8BYjEVfkivPT04ziAue6zyRBtQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-spring/animated": "~10.0.1",
|
||||
"@react-spring/core": "~10.0.1",
|
||||
"@react-spring/shared": "~10.0.1",
|
||||
"@react-spring/types": "~10.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz",
|
||||
@ -766,6 +1029,69 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-color": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-format": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.5.tgz",
|
||||
"integrity": "sha512-mLxrC1MSWupOSncXN/HOlWUAAIffAEBaI4+PKy2uMPsKe4FNZlk7qrbTjmzJXITQQqBHivaks4Td18azgqnotA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-color": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-path": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
|
||||
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-scale": {
|
||||
"version": "4.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
|
||||
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-time": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-scale-chromatic": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
|
||||
"integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-shape": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
|
||||
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-path": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-time": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-1.1.4.tgz",
|
||||
"integrity": "sha512-JIvy2HjRInE+TXOmIGN5LCmeO0hkFZx5f9FZ7kiN+D+YTcc8pptsiLiuHsvwxwC7VVKmJ2ExHUgNlAiV7vQM9g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-time-format": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.3.4.tgz",
|
||||
"integrity": "sha512-xdDXbpVO74EvadI3UDxjxTdR6QIxm1FKzEA/+F8tL4GWWUg/hgvBqf6chql64U5A9ZUGWo7pEu4eNlyLwbKdhg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@ -900,6 +1226,122 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-format": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz",
|
||||
"integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-path": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
|
||||
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-array": "2.10.0 - 3",
|
||||
"d3-format": "1 - 3",
|
||||
"d3-interpolate": "1.2.0 - 3",
|
||||
"d3-time": "2.1.1 - 3",
|
||||
"d3-time-format": "2 - 4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale-chromatic": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
|
||||
"integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3",
|
||||
"d3-interpolate": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale/node_modules/d3-time": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-array": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-shape": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-path": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
|
||||
"integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/d3-time-format": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz",
|
||||
"integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"d3-time": "1 - 2"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@ -1180,6 +1622,21 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@ -1360,6 +1817,16 @@
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-virtualized-auto-sizer": {
|
||||
"version": "1.0.26",
|
||||
"resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.26.tgz",
|
||||
"integrity": "sha512-CblNyiNVw2o+hsa5/49NH2ogGxZ+t+3aweRvNSq7TVjDIlwk7ir4lencEg5HxHeSzwNarSkNkiu0qJSOXtxm5A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz",
|
||||
@ -1459,6 +1926,18 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/use-debounce": {
|
||||
"version": "10.0.5",
|
||||
"resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.5.tgz",
|
||||
"integrity": "sha512-Q76E3lnIV+4YT9AHcrHEHYmAd9LKwUAbPXDm7FlqVGDHiSOhX3RDjT8dm0AxbJup6WgOb1YEcKyCr11kBJR5KQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.4.tgz",
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
"vite": "^7.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nivo/boxplot": "^0.99.0",
|
||||
"axios": "^1.10.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"react": "^19.1.0",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import { ResponsiveBoxPlot } from "@nivo/boxplot";
|
||||
import PointCloud from "../components/PointCloud";
|
||||
import "../style.css";
|
||||
|
||||
@ -7,6 +8,7 @@ const GeneView: React.FC = () => {
|
||||
const [gene, setGene] = useState("");
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [cellData, setCellData] = useState<any[]>([]);
|
||||
const [distributionData, setDistributionData] = useState<any>({ data: [], colorObj: {}, cellTypes: [] });
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [cellLoading, setCellLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
@ -14,6 +16,23 @@ const GeneView: React.FC = () => {
|
||||
const [availableGenes, setAvailableGenes] = useState<string[]>([]);
|
||||
const [selectedStage, setSelectedStage] = useState("CS7");
|
||||
|
||||
// 为细胞类型生成颜色映射(与 PointCloud 组件保持一致)
|
||||
const generateCellTypeColors = (cellTypes: string[]) => {
|
||||
const colorMap = new Map<string, string>();
|
||||
const colors = [
|
||||
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
|
||||
'#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9',
|
||||
'#F8C471', '#82E0AA', '#F1948A', '#85CDCE', '#D7BDE2',
|
||||
'#A9DFBF', '#F9E79F', '#D5A6BD', '#AED6F1', '#E8DAEF'
|
||||
];
|
||||
|
||||
cellTypes.forEach((type, index) => {
|
||||
colorMap.set(type, colors[index % colors.length]);
|
||||
});
|
||||
|
||||
return colorMap;
|
||||
};
|
||||
|
||||
// 动态获取某个阶段的基因列表
|
||||
useEffect(() => {
|
||||
if (!selectedStage) return;
|
||||
@ -58,19 +77,53 @@ const GeneView: React.FC = () => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
setData([]);
|
||||
setDistributionData({ data: [], colorObj: {}, cellTypes: [] });
|
||||
|
||||
try {
|
||||
const res = await axios.get("http://localhost:5000/api/gene_expression", {
|
||||
params: {
|
||||
gene: gene.trim(),
|
||||
stage: selectedStage,
|
||||
},
|
||||
});
|
||||
// 同时请求基因表达和分布数据
|
||||
const [expressionRes, distributionRes] = await Promise.all([
|
||||
axios.get("http://localhost:5000/api/gene_expression", {
|
||||
params: {
|
||||
gene: gene.trim(),
|
||||
stage: selectedStage,
|
||||
},
|
||||
}),
|
||||
axios.get("http://localhost:5000/api/gene_dist", {
|
||||
params: {
|
||||
gene: gene.trim(),
|
||||
stage: selectedStage,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
if (res.data.expression.length === 0) {
|
||||
if (expressionRes.data.expression.length === 0) {
|
||||
setError("No expression data found.");
|
||||
} else {
|
||||
setData(res.data.expression);
|
||||
setData(expressionRes.data.expression);
|
||||
|
||||
// 转换分布数据为 nivo boxplot 格式 - 需要扁平化为原始数据点
|
||||
const boxplotData: any[] = [];
|
||||
const cellTypes = Object.keys(distributionRes.data.distribution).sort();
|
||||
const colorMap = generateCellTypeColors(cellTypes);
|
||||
|
||||
Object.entries(distributionRes.data.distribution).forEach(
|
||||
([cellType, values]: [string, any]) => {
|
||||
values.forEach((value: number) => {
|
||||
boxplotData.push({
|
||||
group: cellType,
|
||||
value: value,
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// 创建颜色对象映射
|
||||
const colorObj: { [key: string]: string } = {};
|
||||
cellTypes.forEach(cellType => {
|
||||
colorObj[cellType] = colorMap.get(cellType) || '#CCCCCC';
|
||||
});
|
||||
|
||||
setDistributionData({ data: boxplotData, colorObj, cellTypes });
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err.response && err.response.data?.error) {
|
||||
@ -145,12 +198,21 @@ const GeneView: React.FC = () => {
|
||||
<div className="section">
|
||||
<h2>3D 可视化</h2>
|
||||
<p style={{ marginBottom: "1.5rem", color: "var(--text-secondary)" }}>
|
||||
下方展示了{selectedStage}发育阶段的细胞类型分布(左)和所选基因的表达模式(右)。
|
||||
下方展示了{selectedStage}发育阶段的细胞类型分布(左)、所选基因的表达模式(中)和基因在各细胞类型中的表达分布(右)。
|
||||
点击任意数据点可查看详细信息。
|
||||
</p>
|
||||
|
||||
{/* 双可视化区域 */}
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "2rem", minHeight: "500px" }}>
|
||||
{/* 三可视化区域 */}
|
||||
<div
|
||||
className="three-column-grid"
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "minmax(400px, 1fr) minmax(400px, 1fr) minmax(450px, 1fr)",
|
||||
gap: "2rem",
|
||||
minHeight: "600px",
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
{/* 细胞类型可视化 */}
|
||||
<div className="visualization-container">
|
||||
<h3 style={{ marginBottom: "1rem", textAlign: "center" }}>细胞类型分布</h3>
|
||||
@ -201,6 +263,86 @@ const GeneView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 基因分布箱线图 */}
|
||||
<div className="visualization-container">
|
||||
<h3 style={{ marginBottom: "1rem", textAlign: "center" }}>基因表达分布</h3>
|
||||
{distributionData.data && distributionData.data.length > 0 ? (
|
||||
<div style={{ height: "480px" }}>
|
||||
<ResponsiveBoxPlot
|
||||
data={distributionData.data}
|
||||
margin={{ top: 40, right: 100, bottom: 80, left: 140 }}
|
||||
minValue="auto"
|
||||
maxValue="auto"
|
||||
padding={0.2}
|
||||
enableGridX={true}
|
||||
enableGridY={false}
|
||||
gridXValues={3}
|
||||
layout="horizontal"
|
||||
axisTop={null}
|
||||
axisRight={null}
|
||||
axisBottom={{
|
||||
tickSize: 5,
|
||||
tickPadding: 12,
|
||||
tickRotation: 0,
|
||||
legend: "表达值",
|
||||
legendPosition: "middle",
|
||||
legendOffset: 60,
|
||||
format: (value: number) => value.toFixed(2),
|
||||
tickValues: 3,
|
||||
}}
|
||||
axisLeft={{
|
||||
tickSize: 5,
|
||||
tickPadding: 10,
|
||||
tickRotation: 0,
|
||||
legend: "细胞类型",
|
||||
legendPosition: "middle",
|
||||
legendOffset: -120,
|
||||
format: (value: string) => value.length > 12 ? `${value.substring(0, 12)}...` : value,
|
||||
}}
|
||||
colors={(boxData: any) => {
|
||||
// 根据 group 获取颜色
|
||||
return distributionData.colorObj[boxData.group] || '#CCCCCC';
|
||||
}}
|
||||
borderRadius={2}
|
||||
borderWidth={2}
|
||||
borderColor={{
|
||||
from: "color",
|
||||
modifiers: [["darker", 0.3]],
|
||||
}}
|
||||
medianWidth={2}
|
||||
medianColor={{
|
||||
from: "color",
|
||||
modifiers: [["darker", 0.3]],
|
||||
}}
|
||||
whiskerEndSize={0.6}
|
||||
whiskerColor={{
|
||||
from: "color",
|
||||
modifiers: [["darker", 0.3]],
|
||||
}}
|
||||
motionConfig="gentle"
|
||||
legends={[]}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="no-data-message">
|
||||
{loading ? (
|
||||
<div>
|
||||
<div style={{ fontSize: "2rem", marginBottom: "1rem" }}>🔄</div>
|
||||
<p>正在加载分布数据...</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div style={{ fontSize: "2rem", marginBottom: "1rem" }}>📈</div>
|
||||
<p>请选择目标基因以查看表达分布</p>
|
||||
<p style={{ fontSize: "0.9rem", color: "var(--text-secondary)" }}>
|
||||
箱线图显示各细胞类型中的表达分布
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -217,7 +359,11 @@ const GeneView: React.FC = () => {
|
||||
</div>
|
||||
<div>
|
||||
<h3>🎨 颜色编码</h3>
|
||||
<p>左侧:不同颜色代表不同细胞类型。右侧:颜色代表基因表达强度,红色表示高表达,蓝色表示低表达。</p>
|
||||
<p>左侧和右侧:相同颜色代表相同细胞类型,保持一致性便于对比。中间:颜色代表基因表达强度,红色表示高表达,蓝色表示低表达。</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>📈 箱线图解读</h3>
|
||||
<p>箱线图显示四分位数分布:箱体表示25%-75%范围,中线为中位数,胡须显示数据范围,点表示异常值。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -115,9 +115,22 @@ body {
|
||||
|
||||
/* ===== 页面容器 ===== */
|
||||
.page-container {
|
||||
max-width: 1200px;
|
||||
max-width: 1800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
.page-container {
|
||||
max-width: 1200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-container {
|
||||
padding: 1rem 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header {
|
||||
@ -436,7 +449,8 @@ button:disabled {
|
||||
|
||||
.visualization-container {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
height: 550px;
|
||||
min-width: 400px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 0.75rem;
|
||||
position: relative;
|
||||
@ -774,3 +788,31 @@ button:disabled {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 响应式网格布局 ===== */
|
||||
@media (max-width: 1200px) {
|
||||
.three-column-grid {
|
||||
grid-template-columns: 1fr 1fr !important;
|
||||
grid-template-rows: auto auto auto;
|
||||
}
|
||||
|
||||
.three-column-grid > div:first-child {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.visualization-container {
|
||||
min-width: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.three-column-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
gap: 1.5rem !important;
|
||||
}
|
||||
|
||||
.visualization-container {
|
||||
height: 450px;
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user