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