Ver Fonte

commit 优化树形关系图

林海 há 1 mês atrás
pai
commit
a311f3ecc4
1 ficheiros alterados com 88 adições e 9 exclusões
  1. 88 9
      templates/tree.html

+ 88 - 9
templates/tree.html

@@ -88,12 +88,9 @@
     /* 男女形状与颜色区分:男性方形(蓝),女性圆形(粉红),添加轻微圆角与投影 */
     .node-male rect { stroke: #3B82F6; fill: #EFF6FF; rx: 8px; ry: 8px; }
     
-    /* 出继节点样式:红色虚线框 */
+    /* 出继节点样式:同色虚线框,仅边框样式区别,颜色与普通节点保持一致 */
     .node-adopted-out rect, .node-adopted-out circle {
-        stroke: #EF4444;
-        stroke-width: 2px;
-        stroke-dasharray: 6, 4;
-        fill: rgba(239, 68, 68, 0.1);
+        stroke-dasharray: 6, 4 !important;
     }
     .node-female circle { stroke: #EC4899; fill: #FDF2F8; }
     
@@ -343,10 +340,15 @@
         // 获取所有父子关系(包括出继/入继)
         const allHierarchicalRelations = relations.filter(r => r.relation_type === 1 || r.relation_type === 2);
         
-        // 对于出继的子女,记录他们入继到的目标
+        // 对于出继的子女,只要有 sub_relation_type===2 的关系即标记;同时尝试找到入继目标名称
         const adoptedOutMap = new Map();
         allHierarchicalRelations.forEach(r => {
             if (r.sub_relation_type === 2) { // 出继
+                // 即便未找到入继关系也先标记(target 置 null)
+                if (!adoptedOutMap.has(r.child_mid)) {
+                    adoptedOutMap.set(r.child_mid, null);
+                }
+                // 尝试找到对应的入继关系,获取养父名
                 const targetRelation = allHierarchicalRelations.find(r2 => r2.child_mid === r.child_mid && r2.sub_relation_type === 3);
                 if (targetRelation) {
                     const targetMember = members.find(m => m.id === targetRelation.parent_mid);
@@ -371,6 +373,51 @@
             target: r.child_mid,
             sub_relation_type: r.sub_relation_type 
         }));
+
+        // ── 推断缺失的入继关系 ──────────────────────────────────────────────
+        // 若同一亲生父的多个出继子女共享同一已知养父,则为缺失入继记录的子女补全推断链接
+        {
+            // 按亲生父分组:parentId → [childId, ...](仅 sub=2 的出继子女)
+            const outByParent = new Map();
+            allHierarchicalRelations.forEach(r => {
+                if (r.sub_relation_type === 2) {
+                    if (!outByParent.has(r.parent_mid)) outByParent.set(r.parent_mid, []);
+                    outByParent.get(r.parent_mid).push(r.child_mid);
+                }
+            });
+            outByParent.forEach((childMids, _parentId) => {
+                // 收集已知养父 ID 集合
+                const knownTargetIds = new Set();
+                childMids.forEach(childId => {
+                    const link = hierarchicalLinks.find(l => l.target === childId && l.sub_relation_type === 3);
+                    if (link) knownTargetIds.add(link.source);
+                });
+                // 只有唯一确定的养父时才推断(避免歧义)
+                if (knownTargetIds.size === 1) {
+                    const adoptiveParentId = [...knownTargetIds][0];
+                    childMids.forEach(childId => {
+                        const exists = hierarchicalLinks.some(l => l.source === adoptiveParentId && l.target === childId && l.sub_relation_type === 3);
+                        if (!exists) {
+                            hierarchicalLinks.push({ source: adoptiveParentId, target: childId, sub_relation_type: 3 });
+                            // 同步更新 adoptedOutTarget
+                            const targetMember = members.find(m => m.id === adoptiveParentId);
+                            if (targetMember && adoptedOutMap.has(childId) && !adoptedOutMap.get(childId)) {
+                                adoptedOutMap.set(childId, targetMember.name);
+                            }
+                        }
+                    });
+                }
+            });
+            // adoptedOutTarget 同步回 nodes
+            nodes.forEach(node => {
+                if (adoptedOutMap.has(node.id)) {
+                    node.adoptedOut = true;
+                    node.adoptedOutTarget = adoptedOutMap.get(node.id);
+                }
+            });
+        }
+        // ────────────────────────────────────────────────────────────────────
+
         const spouseLinks = relations.filter(r => r.relation_type === 10);
         const otherLinks = relations.filter(r => r.relation_type >= 11);
 
@@ -390,7 +437,29 @@
             // 折叠时不递归子代(但仍保留配偶以维持同层显示)
             const children = isCollapsed
                 ? []
-                : childLinks.map(l => buildHierarchy(l.target, processedNodes)).filter(c => c !== null);
+                : childLinks.map(l => {
+                    if (processedNodes.has(l.target)) {
+                        // 入继(sub_relation_type===3)子女已在亲生父处渲染过,
+                        // 在养父下创建浅引用节点(不再递归,避免重复/死循环)
+                        if (l.sub_relation_type === 3) {
+                            const cn = nodes.find(n => n.id === l.target);
+                            if (cn) {
+                                return {
+                                    id: cn.id,
+                                    name: cn.name,
+                                    simplified_name: cn.simplified_name,
+                                    sex: cn.sex,
+                                    adoptedIn: true,
+                                    hasHierarchicalChildren: false,
+                                    isCollapsed: false,
+                                    children: []
+                                };
+                            }
+                        }
+                        return null;
+                    }
+                    return buildHierarchy(l.target, processedNodes);
+                }).filter(c => c !== null);
 
             const spouses = spouseLinks.filter(l => l.parent_mid === nodeId)
                                 .map(l => {
@@ -566,8 +635,13 @@
                     const pSex = node.data.sex;
                     const cSex = child.data && child.data.sex;
                     let label = "亲子";
-                    if (pSex === 1) label = cSex === 1 ? "父子" : "父女";
-                    else if (pSex === 2) label = cSex === 1 ? "母子" : "母女";
+                    if (child.data && child.data.adoptedIn) {
+                        label = cSex === 1 ? "养子" : "养女";
+                    } else if (pSex === 1) {
+                        label = cSex === 1 ? "父子" : "父女";
+                    } else if (pSex === 2) {
+                        label = cSex === 1 ? "母子" : "母女";
+                    }
                     
                     const childLinkMidY = (sibsY + child.y - circleR) / 2;
                     addBadge(g, child.x, childLinkMidY, label);
@@ -633,6 +707,11 @@
         // 图形:男性方形,女性圆形,更符合生物遗传图谱
         node.each(function(d) {
             const el = d3.select(this);
+            // 出继节点 hover 提示
+            if (d.data.adoptedOut) {
+                const tip = d.data.adoptedOutTarget ? `出继给 ${d.data.adoptedOutTarget}` : '出继';
+                el.append('title').text(tip);
+            }
             if (d.data.sex === 1) { 
                 el.append("rect")
                   .attr("x", -circleR).attr("y", -circleR)