Optimize node color by pre-set colors

This commit is contained in:
yangdx
2025-04-06 22:01:05 +08:00
parent 09a6311d9e
commit 9b7a8395b5

View File

@@ -1,7 +1,7 @@
import Graph, { DirectedGraph } from 'graphology' import Graph, { DirectedGraph } from 'graphology'
import { useCallback, useEffect, useRef } from 'react' import { useCallback, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { randomColor, errorMessage } from '@/lib/utils' import { errorMessage } from '@/lib/utils'
import * as Constants from '@/lib/constants' import * as Constants from '@/lib/constants'
import { useGraphStore, RawGraph, RawNodeType, RawEdgeType } from '@/stores/graph' import { useGraphStore, RawGraph, RawNodeType, RawEdgeType } from '@/stores/graph'
import { toast } from 'sonner' import { toast } from 'sonner'
@@ -11,32 +11,115 @@ import { useSettingsStore } from '@/stores/settings'
import seedrandom from 'seedrandom' import seedrandom from 'seedrandom'
// Helper function to generate a color based on type // Predefined node colors - Primary colors
const NODE_COLORS = [
'#0f5870', // Deep Cyan
'#e3493b', // Google Red - geo
'#fdd868', // Yellow - UNKNOWN
'#34A853', // Google Green
'#a64dff', // Purple
'#F39C12', // Orange
'#1ABC9C', // Turquoise - organization
'#1f42ad', // Blue
'#ee8377', // Light Red
'#bf95d0', // Light Violet
'#99cc00', // Yellow Green - tecknology
'#0f705d', // Deep Turquoise
'#E67E22', // Carrot - category
'#568be1', // Light Blue - person
'#803300' // Deep Brown
];
// Extended colors - Used when node types exceed primary colors
const EXTENDED_COLORS = [
'#ff4da6', // Magenta
'#094338', // Deep Green
'#D35400', // Pumpkin
'#002699', // Deep Blue
'#5a2c6d', // Deep Violet
'#996600', // Brown
'#2574A9', // Steel Blue
'#912c21', // Deep Red
'#293618' // Dark Green
];
// 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 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) { if (!nodeType) {
return defaultColor; return defaultColor;
} }
// Get type color map from store
const typeColorMap = useGraphStore.getState().typeColorMap; const typeColorMap = useGraphStore.getState().typeColorMap;
if (!typeColorMap.has(nodeType)) { // If this type already has an assigned color, return it
// Generate a color based on the type string itself for consistency if (typeColorMap.has(nodeType)) {
// Seed the global random number generator based on the node type return typeColorMap.get(nodeType) || defaultColor;
seedrandom(nodeType, { global: true }); }
// Call randomColor without arguments; it will use the globally seeded Math.random()
const newColor = randomColor();
// 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); const newMap = new Map(typeColorMap);
newMap.set(nodeType, newColor); newMap.set(nodeType, newColor);
useGraphStore.setState({ typeColorMap: newMap }); useGraphStore.setState({ typeColorMap: newMap });
return newColor; 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 // Add a single node with "Graph Is Empty" label
emptyGraph.addNode('empty-graph-node', { emptyGraph.addNode('empty-graph-node', {
label: t('graphPanel.emptyGraph'), label: t('graphPanel.emptyGraph'),
color: '#cccccc', // gray color color: '#5D6D7E', // gray color
x: 0.5, x: 0.5,
y: 0.5, y: 0.5,
size: 15, size: 15,
@@ -836,25 +919,25 @@ const useLightrangeGraph = () => {
try { try {
const state = useGraphStore.getState(); const state = useGraphStore.getState();
// 1. 检查节点是否存在 // 1. Check if node exists
if (!sigmaGraph.hasNode(nodeId)) { if (!sigmaGraph.hasNode(nodeId)) {
console.error('Node not found:', nodeId); console.error('Node not found:', nodeId);
return; return;
} }
// 2. 获取要删除的节点 // 2. Get nodes to delete
const nodesToDelete = getNodesThatWillBeDeleted(nodeId, sigmaGraph); const nodesToDelete = getNodesThatWillBeDeleted(nodeId, sigmaGraph);
// 3. 检查是否会删除所有节点 // 3. Check if this would delete all nodes
if (nodesToDelete.size === sigmaGraph.nodes().length) { if (nodesToDelete.size === sigmaGraph.nodes().length) {
toast.error(t('graphPanel.propertiesView.node.deleteAllNodesError')); toast.error(t('graphPanel.propertiesView.node.deleteAllNodesError'));
return; return;
} }
// 4. 清除选中状态 - 这会导致PropertiesView立即关闭 // 4. Clear selection - this will cause PropertiesView to close immediately
state.clearSelection(); state.clearSelection();
// 5. 删除节点和相关边 // 5. Delete nodes and related edges
for (const nodeToDelete of nodesToDelete) { for (const nodeToDelete of nodesToDelete) {
// Remove the node from the sigma graph (this will also remove connected edges) // Remove the node from the sigma graph (this will also remove connected edges)
sigmaGraph.dropNode(nodeToDelete); sigmaGraph.dropNode(nodeToDelete);