Merge pull request #1282 from danielaskdd/main

Optimize node color by pre-set colors
This commit is contained in:
Daniel.y
2025-04-06 23:11:04 +08:00
committed by GitHub
3 changed files with 249 additions and 166 deletions

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,7 +1,7 @@
import Graph, { DirectedGraph } from 'graphology'
import { useCallback, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { randomColor, errorMessage } from '@/lib/utils'
import { errorMessage } from '@/lib/utils'
import * as Constants from '@/lib/constants'
import { useGraphStore, RawGraph, RawNodeType, RawEdgeType } from '@/stores/graph'
import { toast } from 'sonner'
@@ -11,32 +11,115 @@ import { useSettingsStore } from '@/stores/settings'
import seedrandom from 'seedrandom'
// Helper function to generate a color based on type
// Predefined node colors - Primary colors
const NODE_COLORS = [
'#fdd868', // Yellow - UNKNOWN
'#e3493b', // Google Red - geo
'#1212a1', // Deep Cyan - weapon
'#0f705d', // Green - organization
'#a64dff', // Purple - technology
'#f46a9b', // Magenta
'#00bfa0', // Turquoise
'#fdcce5', // Light 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
const EXTENDED_COLORS = [
'#5ad45a', // Light Green
'#5a2c6d', // Deep Violet
'#6c1313', // Dark Red
'#184868', // Dark Cyan
'#996600', // Yellow Brown
'#4421af', // Deep Purple
'#E67E22', // Carrot
'#e61919', // Light Red
];
// All available colors combined
const ALL_COLORS = [...NODE_COLORS, ...EXTENDED_COLORS];
// Helper function to get color based on node type
const getNodeColorByType = (nodeType: string | undefined): string => {
const defaultColor = '#CCCCCC'; // Default color for nodes without a type or undefined type
const defaultColor = '#5D6D7E'; // Default color for nodes without a type or undefined type
// Return default color if node type is undefined
if (!nodeType) {
return defaultColor;
}
// Get type color map from store
const typeColorMap = useGraphStore.getState().typeColorMap;
if (!typeColorMap.has(nodeType)) {
// Generate a color based on the type string itself for consistency
// Seed the global random number generator based on the node type
seedrandom(nodeType, { global: true });
// Call randomColor without arguments; it will use the globally seeded Math.random()
const newColor = randomColor();
// If this type already has an assigned color, return it
if (typeColorMap.has(nodeType)) {
return typeColorMap.get(nodeType) || defaultColor;
}
// Get all currently used colors
const usedColors = new Set<string>();
typeColorMap.forEach(color => {
usedColors.add(color);
});
// Assign color for new node type
// Use a simple hash function to map node type to color index
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
if (!foundUnused) {
newColor = defaultColor;
for (let i = 0; i < EXTENDED_COLORS.length; i++) {
const candidateColor = EXTENDED_COLORS[i];
if (!usedColors.has(candidateColor)) {
newColor = candidateColor;
break;
}
}
}
}
// If all colors are used, we'll still use the hashed color
// In a more advanced implementation, we could create color variants here
// Update color mapping
const newMap = new Map(typeColorMap);
newMap.set(nodeType, newColor);
useGraphStore.setState({ typeColorMap: newMap });
return newColor;
}
// Restore the default random seed if necessary, though usually not required for this use case
// seedrandom(Date.now().toString(), { global: true });
return typeColorMap.get(nodeType) || defaultColor; // Add fallback just in case
};
@@ -408,7 +491,7 @@ const useLightrangeGraph = () => {
// Add a single node with "Graph Is Empty" label
emptyGraph.addNode('empty-graph-node', {
label: t('graphPanel.emptyGraph'),
color: '#cccccc', // gray color
color: '#5D6D7E', // gray color
x: 0.5,
y: 0.5,
size: 15,
@@ -836,25 +919,25 @@ const useLightrangeGraph = () => {
try {
const state = useGraphStore.getState();
// 1. 检查节点是否存在
// 1. Check if node exists
if (!sigmaGraph.hasNode(nodeId)) {
console.error('Node not found:', nodeId);
return;
}
// 2. 获取要删除的节点
// 2. Get nodes to delete
const nodesToDelete = getNodesThatWillBeDeleted(nodeId, sigmaGraph);
// 3. 检查是否会删除所有节点
// 3. Check if this would delete all nodes
if (nodesToDelete.size === sigmaGraph.nodes().length) {
toast.error(t('graphPanel.propertiesView.node.deleteAllNodesError'));
return;
}
// 4. 清除选中状态 - 这会导致PropertiesView立即关闭
// 4. Clear selection - this will cause PropertiesView to close immediately
state.clearSelection();
// 5. 删除节点和相关边
// 5. Delete nodes and related edges
for (const nodeToDelete of nodesToDelete) {
// Remove the node from the sigma graph (this will also remove connected edges)
sigmaGraph.dropNode(nodeToDelete);