This commit is contained in:
2026-07-12 21:26:08 +08:00
commit 9dd41afd48
502 changed files with 129901 additions and 0 deletions
@@ -0,0 +1,186 @@
import {
buildGraphRendererAdapterData,
buildGraphRendererBehaviorContract,
type GraphRendererBehaviorContract
} from "../src/render";
import type {
GraphAggregationMarker,
GraphData,
GraphFocusInput,
GraphPinHint,
GraphTypeFilters,
NodeId,
PinMap,
SelectionInput
} from "../src/types";
export interface VisNetworkTrialOptions {
pins?: PinMap;
selection?: SelectionInput | null;
searchResultIds?: NodeId[];
aggregationMarkers?: GraphAggregationMarker[];
focus?: GraphFocusInput;
typeFilters?: GraphTypeFilters;
}
export interface VisNetworkTrialModel {
nodes: VisNetworkTrialNode[];
edges: VisNetworkTrialEdge[];
communities: VisNetworkTrialCommunity[];
aggregations: VisNetworkTrialAggregation[];
behavior: GraphRendererBehaviorContract;
}
export interface VisNetworkTrialNode {
id: string;
label: string;
x: number;
y: number;
size: number;
color: string;
communityId: string | null;
sourcePath: string;
selected: boolean;
searchHit: boolean;
pinned: boolean;
pinHint: GraphPinHint;
aggregationIds: string[];
}
export interface VisNetworkTrialEdge {
id: string;
from: string;
to: string;
color: string;
width: number;
relationType: string | null;
}
export interface VisNetworkTrialCommunity {
id: string;
label: string;
nodeIds: string[];
selected: boolean;
searchResultIds: string[];
pinnedNodeIds: string[];
}
export interface VisNetworkTrialAggregation {
id: string;
communityId: string | null;
nodeIds: string[];
selectedNodeIds: string[];
searchResultIds: string[];
pinnedNodeIds: string[];
totalCount: number;
}
const COMMUNITY_COLORS = [
"#2563eb",
"#059669",
"#d97706",
"#7c3aed",
"#dc2626",
"#0891b2",
"#4f46e5",
"#65a30d"
];
export function buildVisNetworkTrialModel(data: GraphData, options: VisNetworkTrialOptions = {}): VisNetworkTrialModel {
const adapter = buildGraphRendererAdapterData(data, options);
const adapterNodeById = new Map(adapter.nodes.map((node) => [node.id, node]));
const nodeIds = new Set(data.nodes.map((node) => node.id));
const communityNodeIds = new Map<string, string[]>();
for (const node of data.nodes) {
const communityId = String(node.community ?? "_none");
const list = communityNodeIds.get(communityId) ?? [];
list.push(node.id);
communityNodeIds.set(communityId, list);
}
const nodes = data.nodes.map((node, index): VisNetworkTrialNode => {
const adapterNode = adapterNodeById.get(node.id);
const communityId = node.community == null ? null : String(node.community);
const pinHint = adapterNode?.pinHint ?? {
nodeId: node.id,
wikiPath: node.source_path ?? node.id,
pinned: false,
position: null
};
return {
id: node.id,
label: node.label,
x: finiteNumber(adapterNode?.point.x, finiteNumber(node.x, index % 100)),
y: finiteNumber(adapterNode?.point.y, finiteNumber(node.y, Math.floor(index / 100))),
size: pinHint.pinned ? 8 : adapterNode?.selected ? 8 : adapterNode?.searchHit ? 7 : 4,
color: adapterNode?.selected
? "#ef4444"
: adapterNode?.searchHit
? "#f59e0b"
: colorForCommunity(communityId),
communityId,
sourcePath: adapterNode?.sourcePath ?? node.source_path ?? node.id,
selected: adapterNode?.selected ?? false,
searchHit: adapterNode?.searchHit ?? false,
pinned: pinHint.pinned,
pinHint,
aggregationIds: adapterNode?.aggregationIds ?? []
};
});
const edges = data.edges
.filter((edge) => nodeIds.has(edge.from) && nodeIds.has(edge.to))
.map((edge): VisNetworkTrialEdge => ({
id: edge.id,
from: edge.from,
to: edge.to,
color: "#94a3b8",
width: Math.max(0.25, Math.min(1.5, Number(edge.weight ?? 0.6))),
relationType: edge.relation_type ?? edge.type ?? null
}));
const communities = adapter.communities.map((community): VisNetworkTrialCommunity => ({
id: community.id,
label: community.label,
nodeIds: communityNodeIds.get(community.id) ?? community.nodeIds,
selected: community.selected,
searchResultIds: community.searchResultIds,
pinnedNodeIds: community.pinHints.map((hint) => hint.nodeId)
}));
const aggregations = adapter.aggregations.map((aggregation): VisNetworkTrialAggregation => ({
id: aggregation.id,
communityId: aggregation.communityId,
nodeIds: aggregation.nodeIds,
selectedNodeIds: aggregation.selectedNodeIds,
searchResultIds: aggregation.searchResultIds,
pinnedNodeIds: aggregation.pinnedNodeIds,
totalCount: aggregation.totalCount
}));
return {
nodes,
edges,
communities,
aggregations,
behavior: buildGraphRendererBehaviorContract(adapter, "candidate-global")
};
}
function colorForCommunity(communityId: string | null): string {
if (!communityId) return "#64748b";
const index = Math.abs(hashString(communityId)) % COMMUNITY_COLORS.length;
return COMMUNITY_COLORS[index];
}
function hashString(value: string): number {
let hash = 0;
for (let index = 0; index < value.length; index += 1) {
hash = ((hash << 5) - hash + value.charCodeAt(index)) | 0;
}
return hash;
}
function finiteNumber(value: unknown, fallback: number): number {
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
}