@basic-genomics/hivtrace-viz 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/embed/index.html +104 -3
- package/package.json +1 -1
package/dist/embed/index.html
CHANGED
|
@@ -955,6 +955,11 @@
|
|
|
955
955
|
<!-- 进化树工具栏 -->
|
|
956
956
|
<div class="hivtrace-tree-toolbar"
|
|
957
957
|
style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap;">
|
|
958
|
+
<style>
|
|
959
|
+
.hivtrace-tree-toolbar>* {
|
|
960
|
+
flex-shrink: 0;
|
|
961
|
+
}
|
|
962
|
+
</style>
|
|
958
963
|
<!-- 适应窗口 -->
|
|
959
964
|
<button type="button" class="btn btn-default btn-sm" id="tree_fit_btn" title="适应窗口">
|
|
960
965
|
<i class="fa fa-expand"></i> 适应窗口
|
|
@@ -990,7 +995,7 @@
|
|
|
990
995
|
</span>
|
|
991
996
|
</div>
|
|
992
997
|
<!-- 导出按钮 -->
|
|
993
|
-
<div class="
|
|
998
|
+
<div class="dropdown" style="display: inline-block; position: relative;">
|
|
994
999
|
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown"
|
|
995
1000
|
id="tree_export_btn">
|
|
996
1001
|
<i class="fa fa-download"></i> 导出 <span class="caret"></span>
|
|
@@ -1000,6 +1005,11 @@
|
|
|
1000
1005
|
<li><a href="#" id="tree_export_json">JSON 数据</a></li>
|
|
1001
1006
|
</ul>
|
|
1002
1007
|
</div>
|
|
1008
|
+
<!-- 动画播放按钮 -->
|
|
1009
|
+
<button type="button" class="btn btn-default btn-sm" id="tree_animate_btn" title="播放从根到叶子的动画"
|
|
1010
|
+
style="margin-left: 8px;">
|
|
1011
|
+
<i class="fa fa-play"></i> 播放动画
|
|
1012
|
+
</button>
|
|
1003
1013
|
<!-- 统计信息 -->
|
|
1004
1014
|
<div style="flex: 1;"></div>
|
|
1005
1015
|
<div class="hivtrace-tree-stats" style="display: flex; gap: 12px; font-size: 13px; color: #6b7280;">
|
|
@@ -1048,11 +1058,39 @@
|
|
|
1048
1058
|
<script>
|
|
1049
1059
|
// Cytoscape Stylesheet for Tree
|
|
1050
1060
|
window.TREE_STYLESHEET = [
|
|
1051
|
-
|
|
1061
|
+
// 基础节点样式 - 添加 transition 支持平滑动画(官方最佳实践)
|
|
1062
|
+
{
|
|
1063
|
+
selector: 'node', style: {
|
|
1064
|
+
'font-size': 11, 'text-valign': 'center', 'text-halign': 'right', 'text-margin-x': 5, 'color': '#333', 'min-zoomed-font-size': 8,
|
|
1065
|
+
'transition-property': 'background-color, border-color, border-width, width, height, opacity',
|
|
1066
|
+
'transition-duration': '0.25s',
|
|
1067
|
+
'transition-timing-function': 'ease-out'
|
|
1068
|
+
}
|
|
1069
|
+
},
|
|
1052
1070
|
{ selector: 'node[?is_leaf]', style: { 'background-color': '#2563eb', 'width': 10, 'height': 10 } },
|
|
1053
1071
|
{ selector: 'node[!is_leaf]', style: { 'background-color': '#CBD5E1', 'width': 4, 'height': 4, 'opacity': 0.7, 'label': '', 'border-width': 0 } },
|
|
1054
1072
|
{ selector: 'node[!is_leaf][support]', style: { 'background-color': '#94A3B8', 'width': 5, 'height': 5, 'opacity': 0.8 } },
|
|
1055
|
-
|
|
1073
|
+
// 根节点样式 - 青绿色,与蓝色和谐但有区分
|
|
1074
|
+
{
|
|
1075
|
+
selector: 'node.tree-root', style: {
|
|
1076
|
+
'background-color': '#0d9488', // 青绿色 teal-600
|
|
1077
|
+
'shape': 'ellipse',
|
|
1078
|
+
'width': 12,
|
|
1079
|
+
'height': 12,
|
|
1080
|
+
'opacity': 1,
|
|
1081
|
+
'border-width': 2,
|
|
1082
|
+
'border-color': '#0f766e' // 更深的青绿边框
|
|
1083
|
+
}
|
|
1084
|
+
},
|
|
1085
|
+
// 基础边样式 - 添加 transition 支持
|
|
1086
|
+
{
|
|
1087
|
+
selector: 'edge', style: {
|
|
1088
|
+
'width': 2, 'line-color': '#333', 'target-arrow-color': '#333', 'curve-style': 'bezier', 'opacity': 0.8,
|
|
1089
|
+
'transition-property': 'line-color, width, opacity',
|
|
1090
|
+
'transition-duration': '0.25s',
|
|
1091
|
+
'transition-timing-function': 'ease-out'
|
|
1092
|
+
}
|
|
1093
|
+
},
|
|
1056
1094
|
{ selector: 'node:selected', style: { 'background-color': '#FFD700', 'border-width': 3, 'border-color': '#FF6B6B' } },
|
|
1057
1095
|
{ selector: 'edge:selected', style: { 'line-color': '#FF6B6B', 'width': 3, 'opacity': 1 } },
|
|
1058
1096
|
{ selector: 'node.grayed-out', style: { 'background-color': '#94A3B8', 'border-color': '#CBD5E1', 'opacity': 0.5 } },
|
|
@@ -1143,6 +1181,61 @@
|
|
|
1143
1181
|
var blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
1144
1182
|
var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = (fname || 'tree') + '.json'; a.click(); URL.revokeObjectURL(a.href);
|
|
1145
1183
|
};
|
|
1184
|
+
// 查找根节点(入度为 0 的节点)
|
|
1185
|
+
TreeViz.prototype.findRoot = function () {
|
|
1186
|
+
if (!this.cy) return null;
|
|
1187
|
+
var roots = this.cy.nodes().filter(function (n) { return n.indegree(false) === 0; });
|
|
1188
|
+
return roots.length > 0 ? roots[0] : null;
|
|
1189
|
+
};
|
|
1190
|
+
// 标记根节点
|
|
1191
|
+
TreeViz.prototype.markRoot = function () {
|
|
1192
|
+
if (!this.cy) return null;
|
|
1193
|
+
this.cy.nodes().removeClass('tree-root');
|
|
1194
|
+
var root = this.findRoot();
|
|
1195
|
+
if (root) {
|
|
1196
|
+
root.addClass('tree-root');
|
|
1197
|
+
return root.data();
|
|
1198
|
+
}
|
|
1199
|
+
return null;
|
|
1200
|
+
};
|
|
1201
|
+
// 从根到叶子的渐显动画(先全部置灰,然后逐层恢复原色)
|
|
1202
|
+
TreeViz.prototype.animateFromRoot = function (options) {
|
|
1203
|
+
var self = this;
|
|
1204
|
+
if (!this.cy) return;
|
|
1205
|
+
var opts = options || {};
|
|
1206
|
+
var delay = opts.delay || 120; // 每层延迟(毫秒)
|
|
1207
|
+
|
|
1208
|
+
var root = this.findRoot();
|
|
1209
|
+
if (!root) return;
|
|
1210
|
+
|
|
1211
|
+
// 第一步:全部置灰
|
|
1212
|
+
this.cy.elements().addClass('grayed-out');
|
|
1213
|
+
|
|
1214
|
+
// 使用 Cytoscape.js 官方 BFS API 按层分组
|
|
1215
|
+
var layers = [];
|
|
1216
|
+
this.cy.elements().bfs({
|
|
1217
|
+
roots: root,
|
|
1218
|
+
visit: function (v, e, u, i, depth) {
|
|
1219
|
+
if (!layers[depth]) layers[depth] = { nodes: [], edges: [] };
|
|
1220
|
+
layers[depth].nodes.push(v);
|
|
1221
|
+
// 将连接边归入当前层
|
|
1222
|
+
if (e) {
|
|
1223
|
+
layers[depth].edges.push(e);
|
|
1224
|
+
}
|
|
1225
|
+
},
|
|
1226
|
+
directed: true
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
// 第二步:分层逐渐恢复原色(移除 grayed-out 类)
|
|
1230
|
+
layers.forEach(function (layer, i) {
|
|
1231
|
+
setTimeout(function () {
|
|
1232
|
+
layer.nodes.forEach(function (n) { n.removeClass('grayed-out'); });
|
|
1233
|
+
layer.edges.forEach(function (e) { e.removeClass('grayed-out'); });
|
|
1234
|
+
}, i * delay);
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
return { layerCount: layers.length, totalNodes: this.cy.nodes().length };
|
|
1238
|
+
};
|
|
1146
1239
|
global.TreeViz = TreeViz;
|
|
1147
1240
|
})(window);
|
|
1148
1241
|
</script>
|
|
@@ -1300,6 +1393,8 @@
|
|
|
1300
1393
|
});
|
|
1301
1394
|
|
|
1302
1395
|
tree_viz.init(treeData);
|
|
1396
|
+
// 自动标记根节点(红色菱形)
|
|
1397
|
+
tree_viz.markRoot();
|
|
1303
1398
|
|
|
1304
1399
|
// 获取可用簇
|
|
1305
1400
|
var clusters = tree_viz.getAvailableClusters();
|
|
@@ -1422,6 +1517,12 @@
|
|
|
1422
1517
|
e.preventDefault();
|
|
1423
1518
|
tree_viz.exportJSON(treeData, options && options.exportId ? options.exportId + '_tree' : 'tree');
|
|
1424
1519
|
});
|
|
1520
|
+
|
|
1521
|
+
// 播放动画按钮
|
|
1522
|
+
document.getElementById('tree_animate_btn').addEventListener('click', function (e) {
|
|
1523
|
+
e.preventDefault();
|
|
1524
|
+
tree_viz.animateFromRoot({ delay: 100 });
|
|
1525
|
+
});
|
|
1425
1526
|
}
|
|
1426
1527
|
|
|
1427
1528
|
function HandleAppError(err_string) {
|