Merge pull request #1308 from danielaskdd/main

Add node types and colors
This commit is contained in:
Daniel.y
2025-04-08 13:43:18 +08:00
committed by GitHub
9 changed files with 273 additions and 230 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -8,8 +8,8 @@
<link rel="icon" type="image/svg+xml" href="logo.png" /> <link rel="icon" type="image/svg+xml" href="logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lightrag</title> <title>Lightrag</title>
<script type="module" crossorigin src="/webui/assets/index-DYPw8N_u.js"></script> <script type="module" crossorigin src="/webui/assets/index-Ctzoea9d.js"></script>
<link rel="stylesheet" crossorigin href="/webui/assets/index-WCszRhP4.css"> <link rel="stylesheet" crossorigin href="/webui/assets/index-CTB4Vp_z.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -16,7 +16,9 @@ const PREDEFINED_TYPES = [
'weapon', 'weapon',
'animal', 'animal',
'unknown', 'unknown',
'technology' 'object',
'group',
'technology',
] ]
interface LegendProps { interface LegendProps {
@@ -34,7 +36,7 @@ const Legend: React.FC<LegendProps> = ({ className }) => {
return ( return (
<Card className={`p-2 max-w-xs ${className}`}> <Card className={`p-2 max-w-xs ${className}`}>
<h3 className="text-sm font-medium mb-2">{t('graphPanel.legend')}</h3> <h3 className="text-sm font-medium mb-2">{t('graphPanel.legend')}</h3>
<ScrollArea className="max-h-40"> <ScrollArea className="max-h-80">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
{Array.from(typeColorMap.entries()).map(([type, color]) => ( {Array.from(typeColorMap.entries()).map(([type, color]) => (
<div key={type} className="flex items-center gap-2"> <div key={type} className="flex items-center gap-2">

View File

@@ -11,112 +11,146 @@ import { useSettingsStore } from '@/stores/settings'
import seedrandom from 'seedrandom' import seedrandom from 'seedrandom'
// Predefined node colors - Primary colors const TYPE_SYNONYMS: Record<string, string> = {
const NODE_COLORS = [ 'unknown': 'unknown',
'#fdd868', // Yellow - UNKNOWN '未知': 'unknown',
'#e3493b', // Google Red - geo 'other': 'unknown',
'#1212a1', // Deep Cyan - weapon
'#0f705d', // Green - organization
'#a64dff', // Purple - technology
'#f46a9b', // Magenta
'#00bfa0', // Turquoise
'#fdcce5', // Pale Pink
'#0f558a', // Blue - location
'#b2e061', // Yellow Green
'#bd7ebe', // Light Violet - event
'#439bd6', // Cyan - person
'#094338', // Deep Green
'#dc0ab4', // Pink Red
'#fd7f6f', // Light Red - category
'#b04238', // Brown
];
// Extended colors - Used when node types exceed primary colors 'category': 'category',
const EXTENDED_COLORS = [ '类别': 'category',
'#5ad45a', // Light Green 'type': 'category',
'#5a2c6d', // Deep Violet '分类': 'category',
'#6c1313', // Dark Red
'#184868', // Dark Cyan
'#996600', // Yellow Brown
'#4421af', // Deep Purple
'#E67E22', // Carrot
'#ff1a1a', // Pure Red
];
// All available colors combined 'organization': 'organization',
const ALL_COLORS = [...NODE_COLORS, ...EXTENDED_COLORS]; '组织': 'organization',
'org': 'organization',
'company': 'organization',
'公司': 'organization',
'机构': 'organization',
// Helper function to get color based on node type 'event': 'event',
const getNodeColorByType = (nodeType: string | undefined): string => { '事件': 'event',
const defaultColor = '#5D6D7E'; // Default color for nodes without a type or undefined type 'activity': 'event',
'活动': 'event',
// Return default color if node type is undefined 'person': 'person',
if (!nodeType) { '人物': 'person',
return defaultColor; 'people': 'person',
} 'human': 'person',
'人': 'person',
// Get type color map from store 'animal': 'animal',
const typeColorMap = useGraphStore.getState().typeColorMap; '动物': 'animal',
'creature': 'animal',
'生物': 'animal',
// If this type already has an assigned color, return it 'geo': 'geo',
if (typeColorMap.has(nodeType)) { '地理': 'geo',
return typeColorMap.get(nodeType) || defaultColor; 'geography': 'geo',
} '地域': 'geo',
// Get all currently used colors 'location': 'location',
const usedColors = new Set<string>(); '地点': 'location',
typeColorMap.forEach(color => { 'place': 'location',
usedColors.add(color); 'address': 'location',
}); '位置': 'location',
'地址': 'location',
// Assign color for new node type 'technology': 'technology',
// Use a simple hash function to map node type to color index '技术': 'technology',
const getColorIndex = (str: string): number => { 'tech': 'technology',
let hash = 0; '科技': 'technology',
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i); 'equipment': 'equipment',
hash |= 0; // Convert to 32bit integer '设备': 'equipment',
} 'device': 'equipment',
// Ensure result is positive and within NODE_COLORS range only '装备': 'equipment',
return Math.abs(hash) % NODE_COLORS.length;
'weapon': 'weapon',
'武器': 'weapon',
'arms': 'weapon',
'军火': 'weapon',
'object': 'object',
'物品': 'object',
'stuff': 'object',
'物体': 'object',
'group': 'group',
'群组': 'group',
'community': 'group',
'社区': 'group'
}; };
// Get initial color index from hash // 节点类型到颜色的映射
const colorIndex = getColorIndex(nodeType); const NODE_TYPE_COLORS: Record<string, string> = {
let newColor = NODE_COLORS[colorIndex]; 'unknown': '#f4d371', // Yellow
'category': '#e3493b', // GoogleRed
'organization': '#0f705d', // Green
'event': '#00bfa0', // Turquoise
'person': '#4169E1', // RoyalBlue
'animal': '#84a3e1', // SkyBlue
'geo': '#ff99cc', // Pale Pink
'location': '#cf6d17', // Carrot
'technology': '#b300b3', // Purple
'equipment': '#2F4F4F', // DarkSlateGray
'weapon': '#4421af', // DeepPurple
'object': '#00cc00', // Green
'group': '#0f558a', // NavyBlue
};
// If the color is already used, find the next available color // Extended colors pool - Used for unknown node types
if (usedColors.has(newColor) && usedColors.size < ALL_COLORS.length) { const EXTENDED_COLORS = [
// First try to find an unused color in NODE_COLORS '#5a2c6d', // DeepViolet
let foundUnused = false; '#0000ff', // Blue
for (let i = 0; i < NODE_COLORS.length; i++) { '#cd071e', // ChinaRed
const candidateColor = NODE_COLORS[i]; '#00CED1', // DarkTurquoise
if (!usedColors.has(candidateColor)) { '#9b3a31', // DarkBrown
newColor = candidateColor; '#b2e061', // YellowGreen
foundUnused = true; '#bd7ebe', // LightViolet
break; '#6ef7b3', // LightGreen
} '#003366', // DarkBlue
'#DEB887', // BurlyWood
];
// Select color based on node type
const getNodeColorByType = (nodeType: string | undefined): string => {
const defaultColor = '#5D6D7E';
const normalizedType = nodeType ? nodeType.toLowerCase() : 'unknown';
const typeColorMap = useGraphStore.getState().typeColorMap;
// Return previous color if already mapped
if (typeColorMap.has(normalizedType)) {
return typeColorMap.get(normalizedType) || defaultColor;
} }
// If all NODE_COLORS are used, then try EXTENDED_COLORS const standardType = TYPE_SYNONYMS[normalizedType];
if (!foundUnused) { if (standardType) {
newColor = defaultColor; const color = NODE_TYPE_COLORS[standardType];
for (let i = 0; i < EXTENDED_COLORS.length; i++) { // Update color mapping
const candidateColor = EXTENDED_COLORS[i]; const newMap = new Map(typeColorMap);
if (!usedColors.has(candidateColor)) { newMap.set(normalizedType, color);
newColor = candidateColor; useGraphStore.setState({ typeColorMap: newMap });
break; return color;
}
}
}
} }
// If all colors are used, we'll still use the hashed color // For unpredefind nodeTypes, use extended colors
// In a more advanced implementation, we could create color variants here // Find used extended colors
const usedExtendedColors = new Set(
Array.from(typeColorMap.entries())
.filter(([, color]) => !Object.values(NODE_TYPE_COLORS).includes(color))
.map(([, color]) => color)
);
// Find and use the first unused extended color
const unusedColor = EXTENDED_COLORS.find(color => !usedExtendedColors.has(color));
const newColor = unusedColor || defaultColor;
// Update color mapping // Update color mapping
const newMap = new Map(typeColorMap); const newMap = new Map(typeColorMap);
newMap.set(nodeType, newColor); newMap.set(normalizedType, newColor);
useGraphStore.setState({ typeColorMap: newMap }); useGraphStore.setState({ typeColorMap: newMap });
return newColor; return newColor;

View File

@@ -155,6 +155,8 @@
"weapon": "سلاح", "weapon": "سلاح",
"animal": "حيوان", "animal": "حيوان",
"unknown": "غير معروف", "unknown": "غير معروف",
"object": "مصنوع",
"group": "مجموعة",
"technology": "العلوم" "technology": "العلوم"
}, },
"sideBar": { "sideBar": {

View File

@@ -155,6 +155,7 @@
"weapon": "Weapon", "weapon": "Weapon",
"animal": "Animal", "animal": "Animal",
"unknown": "Unknown", "unknown": "Unknown",
"object": "Object",
"technology": "Technology" "technology": "Technology"
}, },
"sideBar": { "sideBar": {

View File

@@ -155,6 +155,8 @@
"weapon": "Arme", "weapon": "Arme",
"animal": "Animal", "animal": "Animal",
"unknown": "Inconnu", "unknown": "Inconnu",
"object": "Objet",
"group": "Groupe",
"technology": "Technologie" "technology": "Technologie"
}, },
"sideBar": { "sideBar": {

View File

@@ -155,6 +155,8 @@
"weapon": "武器", "weapon": "武器",
"animal": "动物", "animal": "动物",
"unknown": "未知", "unknown": "未知",
"object": "物品",
"group": "群组",
"technology": "技术" "technology": "技术"
}, },
"sideBar": { "sideBar": {