diff --git a/TSUMUGI/app/js/components.js b/TSUMUGI/app/js/components.js
new file mode 100644
index 0000000..0074e1f
--- /dev/null
+++ b/TSUMUGI/app/js/components.js
@@ -0,0 +1,16 @@
+export function calculateConnectedComponents(cy) {
+ const visibleElements = cy.elements(':visible');
+ const connectedComponents = visibleElements.components();
+
+ return connectedComponents.map(component => {
+ let componentObject = {};
+ component.nodes().forEach(node => {
+ const nodeLabel = node.data('label');
+ const nodeAnnotations = Array.isArray(node.data('annotation'))
+ ? node.data('annotation')
+ : [node.data('annotation')];
+ componentObject[nodeLabel] = nodeAnnotations;
+ });
+ return componentObject;
+ });
+}
diff --git a/TSUMUGI/app/js/exporter.js b/TSUMUGI/app/js/exporter.js
index d80729c..baaf4c5 100644
--- a/TSUMUGI/app/js/exporter.js
+++ b/TSUMUGI/app/js/exporter.js
@@ -1,8 +1,10 @@
+import { calculateConnectedComponents } from './components.js';
+
// --------------------------------------------------------
// PNG Exporter
// --------------------------------------------------------
-function exportGraphAsPNG() {
+export function exportGraphAsPNG(cy, file_name) {
const pngContent = cy.png({
scale: 6.25, // Scale to achieve 600 DPI
full: true // Set to true to include the entire graph, even the offscreen parts
@@ -10,7 +12,7 @@ function exportGraphAsPNG() {
const a = document.createElement('a');
a.href = pngContent;
- a.download = 'TSUMUGI_XXX_genesymbol.png';
+ a.download = `${file_name}.png`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
@@ -21,9 +23,9 @@ function exportGraphAsPNG() {
// CSV Exporter
// --------------------------------------------------------
-function exportGraphAsCSV() {
+export function exportGraphAsCSV(cy, file_name) {
// calculateConnectedComponentsを利用して連結成分を取得
- const connected_component = calculateConnectedComponents();
+ const connected_component = calculateConnectedComponents(cy);
// CSVのヘッダー行
let csvContent = "cluster,gene,phenotypes\n";
@@ -45,7 +47,7 @@ function exportGraphAsCSV() {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
- a.download = 'TSUMUGI_XXX_genesymbol.csv';
+ a.download = `${file_name}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
diff --git a/TSUMUGI/app/js/tooltips.js b/TSUMUGI/app/js/tooltips.js
new file mode 100644
index 0000000..19b74e4
--- /dev/null
+++ b/TSUMUGI/app/js/tooltips.js
@@ -0,0 +1,96 @@
+// ############################################################
+// Tooltip Handling Functions
+// ############################################################
+
+// Function to remove all existing tooltips
+export function removeTooltips() {
+ document.querySelectorAll('.cy-tooltip').forEach(el => el.remove());
+}
+
+// Function to create tooltip content for nodes and edges
+function createTooltip(event, cy, map_symbol_to_id) {
+ const data = event.target.data();
+ let tooltipText = '';
+ let pos;
+
+ if (event.target.isNode()) {
+ const annotations = Array.isArray(data.annotation)
+ ? data.annotation.map(anno => '・ ' + anno).join('
')
+ : '・ ' + data.annotation;
+
+ const geneID = map_symbol_to_id[data.label] || "UNKNOWN"; // undefined の場合に備える
+ const url_impc = `https://www.mousephenotype.org/data/genes/${geneID}`;
+ tooltipText = `Phenotypes of ${data.label} KO mice
` + annotations;
+
+ pos = event.target.renderedPosition();
+ } else if (event.target.isEdge()) {
+ const sourceNode = cy.getElementById(data.source).data('label');
+ const targetNode = cy.getElementById(data.target).data('label');
+ const annotations = Array.isArray(data.annotation)
+ ? data.annotation.map(anno => '・ ' + anno).join('
')
+ : '・ ' + data.annotation;
+
+ tooltipText = `Shared phenotypes of ${sourceNode} and ${targetNode} KOs
` + annotations;
+
+ const sourcePos = cy.getElementById(data.source).renderedPosition();
+ const targetPos = cy.getElementById(data.target).renderedPosition();
+ pos = { x: (sourcePos.x + targetPos.x) / 2, y: (sourcePos.y + targetPos.y) / 2 };
+ }
+
+ return { tooltipText, pos };
+}
+
+// Function to show tooltip
+export function showTooltip(event, cy, map_symbol_to_id) {
+ removeTooltips(); // Remove existing tooltips
+
+ const { tooltipText, pos } = createTooltip(event, cy, map_symbol_to_id);
+
+ const tooltip = document.createElement('div');
+ tooltip.classList.add('cy-tooltip');
+ tooltip.innerHTML = tooltipText;
+ Object.assign(tooltip.style, {
+ position: 'absolute',
+ left: `${pos.x + 10}px`,
+ top: `${pos.y + 10}px`,
+ padding: '5px',
+ background: 'white',
+ border: '1px solid #ccc',
+ borderRadius: '5px',
+ boxShadow: '0 2px 10px rgba(0,0,0,0.2)',
+ zIndex: '1000',
+ cursor: 'move',
+ userSelect: 'text'
+ });
+
+ document.querySelector('.cy').appendChild(tooltip);
+ enableTooltipDrag(tooltip);
+}
+
+// Function to enable dragging for tooltips
+function enableTooltipDrag(tooltip) {
+ let isDragging = false;
+ let offset = { x: 0, y: 0 };
+
+ tooltip.addEventListener('mousedown', function (e) {
+ e.stopPropagation();
+ isDragging = true;
+ const rect = tooltip.getBoundingClientRect();
+ offset.x = e.clientX - rect.left;
+ offset.y = e.clientY - rect.top;
+ tooltip.style.cursor = 'grabbing';
+ });
+
+ document.addEventListener('mousemove', function (e) {
+ if (isDragging) {
+ const containerRect = document.querySelector('.cy').getBoundingClientRect();
+ tooltip.style.left = `${e.clientX - offset.x - containerRect.left}px`;
+ tooltip.style.top = `${e.clientY - offset.y - containerRect.top}px`;
+ }
+ });
+
+ document.addEventListener('mouseup', function () {
+ isDragging = false;
+ tooltip.style.cursor = 'move';
+ });
+}
diff --git a/TSUMUGI/app/js/value_scaler.js b/TSUMUGI/app/js/value_scaler.js
new file mode 100644
index 0000000..0a6a28f
--- /dev/null
+++ b/TSUMUGI/app/js/value_scaler.js
@@ -0,0 +1,28 @@
+
+export function scaleToOriginalRange(value, minValue, maxValue) {
+ // Scales a value from the range [1, 10] to a new range [minValue, maxValue].
+ return minValue + (value - 1) * (maxValue - minValue) / 9;
+}
+
+export function scaleValue(value, minValue, maxValue, minScale, maxScale) {
+ // スケールをminScaleとmaxScaleの範囲に変換
+ if (minValue == maxValue) {
+ return (maxScale + minScale) / 2;
+ }
+ return minScale + (value - minValue) * (maxScale - minScale) / (maxValue - minValue);
+}
+
+export function getColorForValue(value) {
+ // value を1-10の範囲から0-1の範囲に変換
+ const ratio = (value - 1) / (10 - 1);
+
+ // Light Yellow から Orange へのグラデーション
+ const r1 = 248, g1 = 229, b1 = 140; // Light Yellow
+ const r2 = 255, g2 = 140, b2 = 0; // Orange
+
+ const r = Math.round(r1 + (r2 - r1) * ratio);
+ const g = Math.round(g1 + (g2 - g1) * ratio);
+ const b = Math.round(b1 + (b2 - b1) * ratio);
+
+ return `rgb(${r}, ${g}, ${b})`;
+}
diff --git a/notebooks/notebools-web/995_generate_html_and_js.ipynb b/notebooks/notebools-web/995_generate_html_and_js.ipynb
index 6ba4245..eb84e18 100644
--- a/notebooks/notebools-web/995_generate_html_and_js.ipynb
+++ b/notebooks/notebools-web/995_generate_html_and_js.ipynb
@@ -9,7 +9,7 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
@@ -22,15 +22,15 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "/mnt/e/TSUMUGI-dev/notebooks/notebools-web\n",
- "/mnt/e/TSUMUGI-dev\n"
+ "/mnt/e/Research/TSUMUGI\n",
+ "/mnt/e/Research/TSUMUGI\n"
]
}
],
@@ -54,7 +54,7 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
@@ -83,7 +83,7 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
@@ -98,7 +98,7 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": 10,
"metadata": {},
"outputs": [
{
@@ -117,7 +117,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
@@ -126,7 +126,7 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
@@ -146,7 +146,7 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
@@ -164,7 +164,7 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
@@ -178,7 +178,7 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": 15,
"metadata": {},
"outputs": [
{
@@ -209,7 +209,7 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": 16,
"metadata": {},
"outputs": [
{
@@ -226,7 +226,7 @@
"3761"
]
},
- "execution_count": 11,
+ "execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
@@ -254,7 +254,7 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
@@ -304,7 +304,7 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
@@ -345,7 +345,7 @@
},
{
"cell_type": "code",
- "execution_count": 83,
+ "execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
@@ -362,7 +362,7 @@
},
{
"cell_type": "code",
- "execution_count": 84,
+ "execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
@@ -381,7 +381,7 @@
},
{
"cell_type": "code",
- "execution_count": 85,
+ "execution_count": 21,
"metadata": {},
"outputs": [
{
@@ -458,13 +458,14 @@
},
{
"cell_type": "code",
- "execution_count": 86,
+ "execution_count": 24,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
+ "Asxl1\n",
"Rab10\n"
]
}
@@ -477,7 +478,7 @@
"########################################\n",
"\n",
"cat data/overlap/available_gene_symbols.txt |\n",
- " grep Rab10 | # <- ここで興味のあるgene symbolを選択\n",
+ " grep -e Rab10 -e Asxl1 | # <- ここで興味のあるgene symbolを選択\n",
" while read gene_symbol; do\n",
" echo $gene_symbol\n",
"\n",
@@ -505,14 +506,14 @@
},
{
"cell_type": "code",
- "execution_count": 87,
+ "execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "2025/03/01 15:45:33\n"
+ "2025/03/02 05:19:07\n"
]
}
],
@@ -539,7 +540,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "env-tsumugi",
+ "display_name": "Python 3",
"language": "python",
"name": "python3"
},
@@ -553,7 +554,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.12.8"
+ "version": "3.10.15"
}
},
"nbformat": 4,
diff --git a/test-tsumugi/app/genesymbol/Asxl1.html b/test-tsumugi/app/genesymbol/Asxl1.html
new file mode 100644
index 0000000..be4a732
--- /dev/null
+++ b/test-tsumugi/app/genesymbol/Asxl1.html
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+ Asxl1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Phenotypes similarity:
+ 1 - 10
+
+
+
+
+
+
+
+
+
+
Node repulsion (Cose only):
+
5
+
+
+
+
+
+
+
+ Genotype specificity:
+
+
+
+
+ Sex specificity:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Similarity of accessory phenotypes
+
+
+
+
+
+
+
+
diff --git a/test-tsumugi/app/genesymbol/Asxl1.js b/test-tsumugi/app/genesymbol/Asxl1.js
new file mode 100644
index 0000000..8d52b90
--- /dev/null
+++ b/test-tsumugi/app/genesymbol/Asxl1.js
@@ -0,0 +1,415 @@
+import { exportGraphAsPNG, exportGraphAsCSV } from '../js/exporter.js';
+import { scaleToOriginalRange, scaleValue, getColorForValue } from '../js/value_scaler.js';
+import { removeTooltips, showTooltip } from '../js/tooltips.js';
+import { calculateConnectedComponents } from '../js/components.js';
+
+// ############################################################################
+// Input handling
+// ############################################################################
+
+
+const elements = (function () {
+ const req = new XMLHttpRequest();
+ let result = null;
+
+ try {
+ req.open("GET", "../../data/genesymbol/Asxl1.json.gz", false);
+ req.overrideMimeType("text/plain; charset=x-user-defined"); // バイナリデータとして扱うための設定
+ req.send(null);
+ if (req.status === 200) {
+ // gzipデータをUint8Arrayに変換
+ const compressedData = new Uint8Array(
+ req.responseText.split("").map(c => c.charCodeAt(0) & 0xff)
+ );
+ // pakoでデコード
+ const decompressedData = pako.ungzip(compressedData, { to: "string" });
+ result = JSON.parse(decompressedData);
+ } else {
+ console.error("HTTP error!! status:", req.status);
+ }
+ } catch (error) {
+ console.error("Failed to load or decode JSON.gz:", error);
+ }
+
+ return result;
+})();
+
+const map_symbol_to_id = (function () {
+ const req = new XMLHttpRequest();
+ let result = null;
+ req.onreadystatechange = function () {
+ if (req.readyState === 4 && req.status === 200) {
+ result = JSON.parse(req.responseText);
+ }
+ };
+ req.open("GET", "../../data/marker_symbol_accession_id.json", false);
+
+
+ req.send(null);
+ return result;
+})();
+
+
+// ############################################################################
+// 遺伝型・正特異的フィルタリング関数
+// ############################################################################
+
+// フィルターフォームの取得
+const filterGenotypeForm = document.getElementById('genotype-filter-form');
+const filterSexForm = document.getElementById('sex-filter-form');
+
+// フィルタリング関数(遺伝型 + 性別)
+function filterElementsByGenotypeAndSex() {
+ const checkedGenotypes = Array.from(filterGenotypeForm.querySelectorAll('input:checked')).map(input => input.value);
+ const checkedSexs = Array.from(filterSexForm.querySelectorAll('input:checked')).map(input => input.value);
+
+ // console.log("検索キーワード (Genotype):", checkedGenotypes);
+ // console.log("検索キーワード (Sex):", checkedSexs);
+
+ let targetElements;
+
+ // もし checkedSexs に Female と Male の両方が含まれていたら、性別のフィルターを無効にし、遺伝型のフィルターのみ適用
+ if (checkedSexs.includes("Female") && checkedSexs.includes("Male")) {
+ // console.log("性別フィルター無効(遺伝型のみ適用)");
+ targetElements = elements;
+ } else {
+ targetElements = elements.map(item => {
+ if (item.data.annotation) {
+ const filteredAnnotations = item.data.annotation.filter(annotation => {
+ const sexMatch = checkedSexs.some(sex => annotation.includes(`${sex}`));
+ return sexMatch;
+ });
+
+ return { ...item, data: { ...item.data, annotation: filteredAnnotations } };
+ }
+ return item;
+ }).filter(item => item.data.annotation && item.data.annotation.length > 0);
+ }
+
+ // 遺伝型フィルターの適用
+ const filteredElements = targetElements.map(item => {
+ if (item.data.annotation) {
+ const filteredAnnotations = item.data.annotation.filter(annotation => {
+ const genotypeMatch = checkedGenotypes.some(genotype => annotation.includes(`${genotype}`));
+ return genotypeMatch;
+ });
+
+ return { ...item, data: { ...item.data, annotation: filteredAnnotations } };
+ }
+ return item;
+ }).filter(item => item.data.annotation && item.data.annotation.length > 0);
+
+ // Cytoscape のデータを更新
+ cy.elements().remove(); // 既存の要素を削除
+ cy.add(filteredElements); // 新しい要素を追加
+ filterElements(); // 孤立ノードを削除
+}
+
+// フォーム変更時にフィルタリング関数を実行
+filterGenotypeForm.addEventListener('change', filterElementsByGenotypeAndSex);
+filterSexForm.addEventListener('change', filterElementsByGenotypeAndSex);
+
+
+// ############################################################################
+// Cytoscape handling
+// ############################################################################
+
+const nodeSizes = elements.filter(ele => ele.data.node_color !== undefined).map(ele => ele.data.node_color);
+const edgeSizes = elements.filter(ele => ele.data.edge_size !== undefined).map(ele => ele.data.edge_size);
+
+const nodeMin = Math.min(...nodeSizes);
+const nodeMax = Math.max(...nodeSizes);
+const edgeMin = Math.min(...edgeSizes);
+const edgeMax = Math.max(...edgeSizes);
+
+function getLayoutOptions() {
+ return {
+ name: currentLayout,
+ nodeRepulsion: nodeRepulsionValue,
+ componentSpacing: componentSpacingValue
+ };
+}
+
+let currentLayout = 'cose';
+
+const nodeRepulsionMin = 1;
+const nodeRepulsionMax = 10000;
+const componentSpacingMin = 1;
+const componentSpacingMax = 200;
+
+let nodeRepulsionValue = scaleToOriginalRange(parseFloat(document.getElementById('nodeRepulsion-slider').value), nodeRepulsionMin, nodeRepulsionMax);
+let componentSpacingValue = scaleToOriginalRange(parseFloat(document.getElementById('nodeRepulsion-slider').value), componentSpacingMin, componentSpacingMax);
+
+const cy = cytoscape({
+ container: document.querySelector('.cy'),
+ elements: elements,
+ style: [
+ {
+ selector: 'node',
+ style: {
+ 'label': 'data(label)',
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'font-size': '20px',
+ 'width': 15,
+ 'height': 15,
+ 'background-color': function (ele) {
+ const color_value = scaleValue(ele.data('node_color'), nodeMin, nodeMax, 1, 10);
+ return getColorForValue(color_value);
+ }
+ }
+ },
+ {
+ selector: 'edge',
+ style: {
+ 'curve-style': 'bezier',
+ 'text-rotation': 'autorotate',
+ 'width': function (ele) {
+ return scaleValue(ele.data('edge_size'), edgeMin, edgeMax, 0.5, 2);
+ }
+ }
+ }
+ ],
+ layout: getLayoutOptions()
+});
+
+
+// // レイアウトが変更されるか、フィルタリングが実行された際に連結成分を計算する関数
+// function calculateConnectedComponents() {
+// // 表示されている要素のみを取得
+// const visibleElements = cy.elements(':visible');
+
+// // 可視状態の要素で連結成分を計算
+// const connectedComponents = visibleElements.components();
+
+// let connected_component = connectedComponents.map(component => {
+// let componentObject = {};
+
+// // ノードを処理
+// component.nodes().forEach(node => {
+// const nodeLabel = node.data('label');
+// const nodeAnnotations = Array.isArray(node.data('annotation'))
+// ? node.data('annotation')
+// : [node.data('annotation')]; // annotation が配列でない場合も考慮
+
+// // ノード名をキー、アノテーションを値とするオブジェクトを作成
+// componentObject[nodeLabel] = nodeAnnotations;
+// });
+
+// return componentObject;
+// });
+
+// // 結果をログに出力(デバッグ用)
+// // console.log('Connected Components (Formatted):', connected_component);
+
+// // 必要に応じて connected_component を他の場所で利用可能にする
+// return connected_component;
+// }
+
+// レイアウト変更後にイベントリスナーを設定
+cy.on('layoutstop', function () {
+ calculateConnectedComponents(cy);
+});
+
+
+// ############################################################################
+// Visualization handling
+// ############################################################################
+
+// --------------------------------------------------------
+// Network layout dropdown
+// --------------------------------------------------------
+
+document.getElementById('layout-dropdown').addEventListener('change', function () {
+ currentLayout = this.value;
+ cy.layout({ name: currentLayout }).run();
+});
+
+// --------------------------------------------------------
+// Initialization of the Slider for Phenotypes similarity
+// --------------------------------------------------------
+const edgeSlider = document.getElementById('filter-edge-slider');
+noUiSlider.create(edgeSlider, {
+ start: [1, 10],
+ connect: true,
+ range: {
+ 'min': 1,
+ 'max': 10
+ },
+ step: 1
+});
+
+function filterElements() {
+ const edgeSliderValues = edgeSlider.noUiSlider.get().map(parseFloat);
+
+ const edgeMinValue = scaleToOriginalRange(edgeSliderValues[0], edgeMin, edgeMax);
+ const edgeMaxValue = scaleToOriginalRange(edgeSliderValues[1], edgeMin, edgeMax);
+
+ cy.nodes().forEach(function (node) {
+ node.style('display', 'element');
+ });
+
+ // Filter edges based on size
+ cy.edges().forEach(function (edge) {
+ const edgeSize = edge.data('edge_size');
+ const sourceNode = cy.getElementById(edge.data('source'));
+ const targetNode = cy.getElementById(edge.data('target'));
+
+ if (sourceNode.style('display') === 'element' && targetNode.style('display') === 'element' &&
+ edgeSize >= edgeMinValue && edgeSize <= edgeMaxValue) {
+ edge.style('display', 'element');
+ } else {
+ edge.style('display', 'none');
+ }
+ });
+
+ // calculateConnectedComponentsを利用して連結成分を取得
+ const connected_component = calculateConnectedComponents(cy);
+
+ // node_colorが1のノードを含む連結成分のみを選択
+ const componentsWithNodeColor1 = connected_component.filter(component => {
+ return Object.keys(component).some(nodeLabel => {
+ const node = cy.$(`node[label="${nodeLabel}"]`);
+ return node.data('node_color') === 1;
+ });
+ });
+
+ // すべてのノードとエッジを一旦非表示にする
+ cy.nodes().style('display', 'none');
+ cy.edges().style('display', 'none');
+
+ // node_colorが1のノードを含む連結成分のみ表示
+ componentsWithNodeColor1.forEach(component => {
+ Object.keys(component).forEach(nodeLabel => {
+ const node = cy.$(`node[label="${nodeLabel}"]`);
+ node.style('display', 'element');
+ node.connectedEdges().style('display', 'element');
+ });
+ });
+
+ cy.nodes().forEach(function (node) {
+ const connectedEdges = node.connectedEdges().filter(edge => edge.style('display') === 'element');
+ if (connectedEdges.length === 0) {
+ node.style('display', 'none'); // Hide node if no connected edges
+ }
+ });
+
+ // Reapply layout after filtering
+ cy.layout(getLayoutOptions()).run();
+}
+
+// --------------------------------------------------------
+// Update the slider values when the sliders are moved
+// --------------------------------------------------------
+
+edgeSlider.noUiSlider.on('update', function (values) {
+ const intValues = values.map(value => Math.round(value));
+ document.getElementById('edge-size-value').textContent = intValues.join(' - ');
+ filterElements();
+});
+
+
+// ############################################################################
+// Cytoscape's visualization setting
+// ############################################################################
+
+// --------------------------------------------------------
+// Slider for Font size
+// --------------------------------------------------------
+const fontSizeSlider = document.getElementById('font-size-slider');
+noUiSlider.create(fontSizeSlider, {
+ start: 20,
+ connect: [true, false],
+ range: {
+ 'min': 1,
+ 'max': 50
+ },
+ step: 1
+});
+fontSizeSlider.noUiSlider.on('update', function (value) {
+ const intValues = Math.round(value);
+ document.getElementById('font-size-value').textContent = intValues;
+ cy.style().selector('node').style('font-size', intValues + 'px').update();
+});
+
+// --------------------------------------------------------
+// Slider for Edge width
+// --------------------------------------------------------
+const edgeWidthSlider = document.getElementById('edge-width-slider');
+noUiSlider.create(edgeWidthSlider, {
+ start: 5,
+ connect: [true, false],
+ range: {
+ 'min': 1,
+ 'max': 10
+ },
+ step: 1
+});
+edgeWidthSlider.noUiSlider.on('update', function (value) {
+ const intValues = Math.round(value);
+ document.getElementById('edge-width-value').textContent = intValues;
+ cy.style().selector('edge').style('width', function (ele) {
+ return scaleValue(ele.data('edge_size'), edgeMin, edgeMax, 0.5, 2) * intValues;
+ }).update();
+});
+
+// --------------------------------------------------------
+// Slider for Node repulsion
+// --------------------------------------------------------
+const nodeRepulsionSlider = document.getElementById('nodeRepulsion-slider');
+noUiSlider.create(nodeRepulsionSlider, {
+ start: 5,
+ connect: [true, false],
+ range: {
+ 'min': 1,
+ 'max': 10
+ },
+ step: 1
+});
+nodeRepulsionSlider.noUiSlider.on('update', function (value) {
+ const intValues = Math.round(value);
+ nodeRepulsionValue = scaleToOriginalRange(parseFloat(intValues), nodeRepulsionMin, nodeRepulsionMax);
+ componentSpacingValue = scaleToOriginalRange(parseFloat(intValues), componentSpacingMin, componentSpacingMax);
+ document.getElementById('node-repulsion-value').textContent = intValues;
+ cy.layout(getLayoutOptions()).run();
+});
+
+
+// ############################################################################
+// Tooltip handling
+// ############################################################################
+
+// Show tooltip on tap
+cy.on('tap', 'node, edge', function (event) {
+ showTooltip(event, cy, map_symbol_to_id);
+});
+
+// Hide tooltip when tapping on background
+cy.on('tap', function (event) {
+ if (event.target === cy) {
+ removeTooltips();
+ }
+});
+
+// ############################################################################
+// Exporter
+// ############################################################################
+
+const file_name = 'TSUMUGI_Asxl1';
+
+// --------------------------------------------------------
+// PNG Exporter
+// --------------------------------------------------------
+
+document.getElementById('export-png').addEventListener('click', function () {
+ exportGraphAsPNG(cy, file_name);
+});
+
+
+// --------------------------------------------------------
+// CSV Exporter
+// --------------------------------------------------------
+
+document.getElementById('export-csv').addEventListener('click', function () {
+ exportGraphAsCSV(cy, file_name);
+});
diff --git a/test-tsumugi/data/genesymbol/Asxl1.json.gz b/test-tsumugi/data/genesymbol/Asxl1.json.gz
new file mode 100644
index 0000000..d5c5da1
Binary files /dev/null and b/test-tsumugi/data/genesymbol/Asxl1.json.gz differ
diff --git a/test-tsumugi/data/genesymbol/Rab10.json.gz b/test-tsumugi/data/genesymbol/Rab10.json.gz
index 57b9455..5d3417b 100644
Binary files a/test-tsumugi/data/genesymbol/Rab10.json.gz and b/test-tsumugi/data/genesymbol/Rab10.json.gz differ
diff --git a/test-tsumugi/data/phenotype/increased_fasting_circulating_glucose_level.json.gz b/test-tsumugi/data/phenotype/increased_fasting_circulating_glucose_level.json.gz
index 45ca8a8..7e41e2d 100644
Binary files a/test-tsumugi/data/phenotype/increased_fasting_circulating_glucose_level.json.gz and b/test-tsumugi/data/phenotype/increased_fasting_circulating_glucose_level.json.gz differ
diff --git a/test-tsumugi/data/phenotype/male_infertility.json.gz b/test-tsumugi/data/phenotype/male_infertility.json.gz
index a69fe56..a2db544 100644
Binary files a/test-tsumugi/data/phenotype/male_infertility.json.gz and b/test-tsumugi/data/phenotype/male_infertility.json.gz differ
diff --git a/test-tsumugi/data/phenotype/preweaning_lethality,_complete_penetrance.json.gz b/test-tsumugi/data/phenotype/preweaning_lethality,_complete_penetrance.json.gz
index 54337df..7c2a8fb 100644
Binary files a/test-tsumugi/data/phenotype/preweaning_lethality,_complete_penetrance.json.gz and b/test-tsumugi/data/phenotype/preweaning_lethality,_complete_penetrance.json.gz differ
diff --git a/test-tsumugi/data/phenotype/preweaning_lethality,_incomplete_penetrance.json.gz b/test-tsumugi/data/phenotype/preweaning_lethality,_incomplete_penetrance.json.gz
index 3ab348d..a5587bc 100644
Binary files a/test-tsumugi/data/phenotype/preweaning_lethality,_incomplete_penetrance.json.gz and b/test-tsumugi/data/phenotype/preweaning_lethality,_incomplete_penetrance.json.gz differ
diff --git a/test-tsumugi/index.html b/test-tsumugi/index.html
index 9d62157..c9cc0e5 100644
--- a/test-tsumugi/index.html
+++ b/test-tsumugi/index.html
@@ -92,10 +92,9 @@