@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.
@@ -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="input-group-btn">
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
- { selector: 'node', style: { 'font-size': 11, 'text-valign': 'center', 'text-halign': 'right', 'text-margin-x': 5, 'color': '#333', 'min-zoomed-font-size': 8 } },
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
- { selector: 'edge', style: { 'width': 2, 'line-color': '#333', 'target-arrow-color': '#333', 'curve-style': 'bezier', 'opacity': 0.8 } },
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basic-genomics/hivtrace-viz",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "HIV-TRACE molecular transmission network visualization with React integration",
5
5
  "engines": {
6
6
  "node": ">=18"