Optimize node color by pre-set colors
This commit is contained in:
@@ -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);
|
||||||
|
Reference in New Issue
Block a user