Better Style Designed by Gemini

This commit is contained in:
2025-12-04 02:29:50 +08:00
parent 534fdbbcc4
commit 9079f7a8a9
13 changed files with 308 additions and 130 deletions

View File

@@ -1,12 +1,14 @@
import { QueryNode } from './nodes/QueryNode'; import { QueryNode } from './nodes/QueryNode';
import { CategoryHeaderNode } from './nodes/CategoryHeaderNode'; import { CategoryHeaderNode } from './nodes/CategoryHeaderNode';
import { AttributeNode } from './nodes/AttributeNode'; import { AttributeNode } from './nodes/AttributeNode';
import { GroupNode } from './nodes/GroupNode';
export const nodeTypes = { export const nodeTypes = {
query: QueryNode, query: QueryNode,
categoryHeader: CategoryHeaderNode, categoryHeader: CategoryHeaderNode,
attribute: AttributeNode, attribute: AttributeNode,
group: GroupNode,
}; };
export { QueryNode, CategoryHeaderNode, AttributeNode }; export { QueryNode, CategoryHeaderNode, AttributeNode, GroupNode };
export { useDAGLayout } from './useDAGLayout'; export { useDAGLayout } from './useDAGLayout';

View File

@@ -18,19 +18,25 @@ export const AttributeNode = memo(({ data }: AttributeNodeProps) => {
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
style={{ style={{
padding: '6px 12px', padding: '8px 16px',
borderRadius: 6, borderRadius: 8,
background: fillColor, background: fillColor,
border: `${isHovered ? 3 : 2}px solid ${strokeColor}`, border: '1px solid transparent',
boxShadow: isHovered
? `0 4px 12px ${strokeColor}66`
: `0 2px 4px ${strokeColor}33`,
color: '#fff', color: '#fff',
fontSize: `${fontSize}px`, fontSize: `${fontSize}px`,
fontWeight: 500, fontWeight: 500,
textAlign: 'center', textAlign: 'center',
cursor: 'pointer', cursor: 'pointer',
transition: 'border-width 0.2s ease, filter 0.2s ease', transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
filter: isHovered ? 'brightness(1.12)' : 'none', transform: isHovered ? 'translateY(-2px)' : 'translateY(0)',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
userSelect: 'none', userSelect: 'none',
width: 240,
overflow: 'hidden',
textOverflow: 'ellipsis',
}} }}
> >
{label} {label}

View File

@@ -17,17 +17,22 @@ export const CategoryHeaderNode = memo(({ data }: CategoryHeaderNodeProps) => {
style={{ style={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: 4, justifyContent: 'center',
padding: '4px 10px', gap: 6,
borderRadius: 4, padding: '8px 16px',
background: color, borderRadius: 20,
border: isFixed ? 'none' : `2px dashed ${isDark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.3)'}`, background: isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.03)',
color: '#fff', // border: `1px solid ${color}`, // Removed border to reduce abruptness
fontSize: '13px', color: color,
fontWeight: 'bold', fontSize: '14px',
fontWeight: 700,
textAlign: 'center', textAlign: 'center',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
userSelect: 'none', userSelect: 'none',
boxShadow: '0 2px 8px rgba(0,0,0,0.05)',
width: 260,
overflow: 'hidden',
textOverflow: 'ellipsis',
}} }}
> >
{label} {label}
@@ -35,10 +40,12 @@ export const CategoryHeaderNode = memo(({ data }: CategoryHeaderNodeProps) => {
<span <span
style={{ style={{
fontSize: '10px', fontSize: '10px',
padding: '1px 4px', padding: '2px 6px',
borderRadius: 3, borderRadius: 10,
background: isDark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.15)', background: color,
marginLeft: 2, color: '#fff',
marginLeft: 4,
fontWeight: 600,
}} }}
> >
AI AI

View File

@@ -0,0 +1,57 @@
import { memo } from 'react';
interface GroupNodeProps {
data: {
label: string;
color: string;
width: number;
height: number;
isDark: boolean;
};
}
export const GroupNode = memo(({ data }: GroupNodeProps) => {
const { label, color, width, height, isDark } = data;
return (
<div
className="group-node-background"
style={{
width,
height,
borderRadius: 16,
background: isDark
? `linear-gradient(180deg, ${color}25 0%, ${color}10 100%)`
: `linear-gradient(180deg, ${color}20 0%, ${color}08 100%)`,
border: `2px solid ${color}40`,
boxShadow: isDark
? `0 0 20px ${color}15, inset 0 0 30px ${color}08`
: `0 0 20px ${color}10, inset 0 0 30px ${color}05`,
transition: 'all 0.3s ease',
pointerEvents: 'none', // Allow clicks to pass through to nodes
}}
>
{label && (
<div
style={{
position: 'absolute',
top: -24,
left: 0,
width: '100%',
textAlign: 'center',
color: color,
fontSize: '14px',
fontWeight: 600,
opacity: 0.8,
textTransform: 'uppercase',
letterSpacing: '1px',
}}
>
{label}
</div>
)}
</div>
);
});
GroupNode.displayName = 'GroupNode';

View File

@@ -72,7 +72,7 @@ export function useDAGLayout(
nodesByCategory[node.category].push(node); nodesByCategory[node.category].push(node);
} }
// Sort nodes within each category by order, then re-index locally // Sort nodes within each category by order
for (const cat of Object.keys(nodesByCategory)) { for (const cat of Object.keys(nodesByCategory)) {
nodesByCategory[cat].sort((a, b) => a.order - b.order); nodesByCategory[cat].sort((a, b) => a.order - b.order);
} }
@@ -83,24 +83,66 @@ export function useDAGLayout(
1 1
); );
const maxTotalHeight = maxNodesInColumn * (nodeHeight + nodeSpacing) - nodeSpacing; const maxTotalHeight = maxNodesInColumn * (nodeHeight + nodeSpacing) - nodeSpacing;
const contentStartY = headerHeight + headerGap; const contentStartY = headerHeight + headerGap + 20; // Added extra top padding
// Layout constants - UNIFORM spacing for all columns // Layout constants - INCREASED spacing
const colStep = 160; // Distance between column left edges (uniform for all) const colStep = 340; // Reduced from 400
const nodeWidth = 240;
const headerWidth = 260;
const groupWidth = 300; // Reduced from 360 to be less "too wide"
// Helper function for column X position // Helper function for column X position (Center of the column)
const getColumnX = (colIndex: number) => colIndex * colStep; const getColumnX = (colIndex: number) => colIndex * colStep;
// 0. Add Group Nodes (Backgrounds) - Rendered first
sortedCategories.forEach((cat, colIndex) => {
const categoryNodes = nodesByCategory[cat.name] || [];
const columnX = getColumnX(colIndex + 1); // +1 because column 0 is query
// Calculate group height to include header and attributes
const nodesCount = categoryNodes.length;
const nodesHeight = Math.max(
nodesCount * (nodeHeight + nodeSpacing) - nodeSpacing,
0
);
// Start group above the header (header is at y=0)
const groupStartY = -20;
const groupPaddingBottom = 20;
// Total height = distance from startY to contentStart + nodes height + bottom padding
const groupHeight = (contentStartY - groupStartY) + nodesHeight + groupPaddingBottom;
nodes.push({
id: `group-${cat.name}`,
type: 'group',
position: {
x: columnX - groupWidth / 2, // Centered
y: groupStartY
},
data: {
label: '', // Label is handled by header node
color: categoryColors[cat.name]?.fill || '#666',
width: groupWidth,
height: Math.max(groupHeight, 100), // Ensure min height
isDark,
},
draggable: false,
selectable: false,
zIndex: -1,
});
});
// 1. Add Query Node (column 0, centered vertically) // 1. Add Query Node (column 0, centered vertically)
const queryY = contentStartY + (maxTotalHeight - nodeHeight) / 2; const queryY = contentStartY + (maxTotalHeight - nodeHeight) / 2;
nodes.push({ nodes.push({
id: 'query-node', id: 'query-node',
type: 'query', type: 'query',
position: { x: getColumnX(0), y: queryY }, position: { x: getColumnX(0) - 60, y: queryY }, // Assuming query node width ~120
data: { data: {
label: data.query, label: data.query,
isDark, isDark,
fontSize, fontSize: fontSize + 2,
}, },
draggable: false, draggable: false,
selectable: false, selectable: false,
@@ -108,11 +150,11 @@ export function useDAGLayout(
// 2. Add Category Headers (starting from column 1) // 2. Add Category Headers (starting from column 1)
sortedCategories.forEach((cat, colIndex) => { sortedCategories.forEach((cat, colIndex) => {
const columnX = getColumnX(colIndex + 1); // +1 because column 0 is query const columnX = getColumnX(colIndex + 1);
nodes.push({ nodes.push({
id: `header-${cat.name}`, id: `header-${cat.name}`,
type: 'categoryHeader', type: 'categoryHeader',
position: { x: columnX, y: 0 }, position: { x: columnX - headerWidth / 2, y: 0 }, // Centered
data: { data: {
label: cat.name, label: cat.name,
color: categoryColors[cat.name]?.fill || '#666', color: categoryColors[cat.name]?.fill || '#666',
@@ -121,20 +163,21 @@ export function useDAGLayout(
}, },
draggable: false, draggable: false,
selectable: false, selectable: false,
zIndex: 10,
}); });
}); });
// 3. Add Attribute Nodes // 3. Add Attribute Nodes
sortedCategories.forEach((cat, colIndex) => { sortedCategories.forEach((cat, colIndex) => {
const categoryNodes = nodesByCategory[cat.name] || []; const categoryNodes = nodesByCategory[cat.name] || [];
const columnX = getColumnX(colIndex + 1); // +1 because column 0 is query const columnX = getColumnX(colIndex + 1);
categoryNodes.forEach((node, rowIndex) => { categoryNodes.forEach((node, rowIndex) => {
const y = contentStartY + rowIndex * (nodeHeight + nodeSpacing); const y = contentStartY + rowIndex * (nodeHeight + nodeSpacing);
nodes.push({ nodes.push({
id: node.id, id: node.id,
type: 'attribute', type: 'attribute',
position: { x: columnX, y }, position: { x: columnX - nodeWidth / 2, y }, // Centered
data: { data: {
label: node.name, label: node.name,
fillColor: categoryColors[node.category]?.fill || '#666', fillColor: categoryColors[node.category]?.fill || '#666',
@@ -143,6 +186,7 @@ export function useDAGLayout(
}, },
draggable: false, draggable: false,
selectable: false, selectable: false,
zIndex: 5,
}); });
}); });
}); });

View File

@@ -5,6 +5,7 @@ import { CategoryNode } from './nodes/CategoryNode';
import { OriginalAttributeNode } from './nodes/OriginalAttributeNode'; import { OriginalAttributeNode } from './nodes/OriginalAttributeNode';
import { DividerNode } from './nodes/DividerNode'; import { DividerNode } from './nodes/DividerNode';
import { QueryNode } from '../dag/nodes/QueryNode'; import { QueryNode } from '../dag/nodes/QueryNode';
import { GroupNode } from '../dag/nodes/GroupNode';
export const transformationNodeTypes = { export const transformationNodeTypes = {
query: QueryNode, query: QueryNode,
@@ -14,6 +15,7 @@ export const transformationNodeTypes = {
description: DescriptionNode, description: DescriptionNode,
originalAttribute: OriginalAttributeNode, originalAttribute: OriginalAttributeNode,
divider: DividerNode, divider: DividerNode,
group: GroupNode,
}; };
export { export {

View File

@@ -18,22 +18,24 @@ export const DescriptionNode = memo(({ data }: DescriptionNodeProps) => {
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
style={{ style={{
padding: '10px 14px', padding: '16px',
borderRadius: 8, borderRadius: 12,
background: isDark background: isDark
? 'linear-gradient(135deg, rgba(82, 196, 26, 0.2) 0%, rgba(82, 196, 26, 0.1) 100%)' ? 'linear-gradient(135deg, rgba(82, 196, 26, 0.15) 0%, rgba(82, 196, 26, 0.05) 100%)'
: 'linear-gradient(135deg, rgba(82, 196, 26, 0.15) 0%, rgba(82, 196, 26, 0.05) 100%)', : 'linear-gradient(135deg, rgba(82, 196, 26, 0.08) 0%, rgba(255, 255, 255, 0.8) 100%)',
border: `2px solid ${isHovered ? '#52c41a' : '#52c41a80'}`, border: `1px solid ${isHovered ? '#52c41a66' : '#52c41a33'}`,
color: isDark ? '#fff' : '#333', color: isDark ? '#fff' : '#333',
fontSize: '12px', fontSize: '13px',
width: 400, width: 400,
minHeight: 50, minHeight: 50,
cursor: 'pointer', cursor: 'pointer',
transition: 'all 0.2s ease', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
userSelect: 'none', userSelect: 'none',
boxShadow: isHovered boxShadow: isHovered
? `0 4px 12px ${isDark ? 'rgba(82, 196, 26, 0.4)' : 'rgba(82, 196, 26, 0.25)'}` ? `0 8px 20px ${isDark ? 'rgba(82, 196, 26, 0.2)' : 'rgba(82, 196, 26, 0.15)'}`
: `0 2px 6px ${isDark ? 'rgba(0, 0, 0, 0.3)' : 'rgba(0, 0, 0, 0.1)'}`, : `0 4px 12px ${isDark ? 'rgba(0, 0, 0, 0.2)' : 'rgba(0, 0, 0, 0.05)'}`,
backdropFilter: 'blur(4px)',
transform: isHovered ? 'translateY(-2px)' : 'translateY(0)',
}} }}
> >
<div <div

View File

@@ -22,65 +22,65 @@ export const ExpertKeywordNode = memo(({ data }: ExpertKeywordNodeProps) => {
position: 'relative', position: 'relative',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: 4, gap: 6,
padding: '6px 12px', padding: '10px 16px',
paddingRight: '36px', paddingRight: '40px',
marginTop: 18, marginTop: 20,
borderRadius: 6, borderRadius: 12,
background: isDark background: isDark
? 'linear-gradient(135deg, rgba(24, 144, 255, 0.15) 0%, rgba(255, 255, 255, 0.1) 100%)' ? 'linear-gradient(135deg, rgba(24, 144, 255, 0.15) 0%, rgba(24, 144, 255, 0.05) 100%)'
: 'linear-gradient(135deg, rgba(24, 144, 255, 0.1) 0%, rgba(0, 0, 0, 0.03) 100%)', : 'linear-gradient(135deg, rgba(24, 144, 255, 0.08) 0%, rgba(255, 255, 255, 0.8) 100%)',
border: `2px solid ${color}`, border: `1px solid ${color}44`,
borderWidth: isHovered ? 3 : 2, boxShadow: isHovered
color: isDark ? '#fff' : '#333', ? `0 8px 20px ${color}33`
fontSize: '13px', : `0 4px 12px ${color}1a`,
color: isDark ? '#fff' : '#1f1f1f',
fontSize: '14px',
fontWeight: 500, fontWeight: 500,
textAlign: 'center', textAlign: 'left',
cursor: 'pointer', cursor: 'pointer',
transition: 'all 0.2s ease', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
userSelect: 'none', userSelect: 'none',
filter: isHovered ? 'brightness(1.1)' : 'none', transform: isHovered ? 'translateY(-2px)' : 'translateY(0)',
boxShadow: isDark backdropFilter: 'blur(4px)',
? '0 2px 8px rgba(24, 144, 255, 0.2)'
: '0 2px 8px rgba(24, 144, 255, 0.15)',
}} }}
> >
{/* Expert Badge - positioned above the node */} {/* Expert Badge - positioned above the node */}
<div <div
style={{ style={{
position: 'absolute', position: 'absolute',
top: -18, top: -20,
left: 0, left: 0,
padding: '2px 6px', padding: '2px 8px',
borderRadius: 3, borderRadius: 12,
background: isDark background: color,
? 'rgba(24, 144, 255, 0.8)'
: 'rgba(24, 144, 255, 0.9)',
color: '#fff', color: '#fff',
fontSize: '10px', fontSize: '11px',
fontWeight: 500, fontWeight: 600,
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}} }}
> >
{expertName} {expertName}
</div> </div>
{/* Keyword Label */} {/* Keyword Label */}
<div>{label}</div> <div style={{ lineHeight: 1.4 }}>{label}</div>
{/* NEW Badge - positioned below the node */} {/* NEW Badge - positioned inside */}
<span <span
style={{ style={{
position: 'absolute', position: 'absolute',
bottom: -14, top: 10,
right: 0, right: 10,
padding: '2px 5px', padding: '2px 6px',
borderRadius: 3, borderRadius: 4,
background: 'linear-gradient(135deg, #52c41a 0%, #73d13d 100%)', background: 'rgba(82, 196, 26, 0.15)',
color: '#fff', color: '#52c41a',
fontSize: '9px', border: '1px solid rgba(82, 196, 26, 0.3)',
fontWeight: 600, fontSize: '10px',
fontWeight: 700,
}} }}
> >
NEW NEW

View File

@@ -18,42 +18,42 @@ export const KeywordNode = memo(({ data }: KeywordNodeProps) => {
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
style={{ style={{
position: 'relative', position: 'relative',
padding: '6px 12px', padding: '10px 16px',
paddingRight: '36px', paddingRight: '40px',
borderRadius: 6, borderRadius: 12,
background: isDark background: isDark
? 'linear-gradient(135deg, rgba(250, 173, 20, 0.15) 0%, rgba(255, 255, 255, 0.1) 100%)' ? 'linear-gradient(135deg, rgba(250, 173, 20, 0.15) 0%, rgba(250, 173, 20, 0.05) 100%)'
: 'linear-gradient(135deg, rgba(250, 173, 20, 0.1) 0%, rgba(0, 0, 0, 0.03) 100%)', : 'linear-gradient(135deg, rgba(250, 173, 20, 0.08) 0%, rgba(255, 255, 255, 0.8) 100%)',
border: `2px solid ${color}`, border: `1px solid ${color}44`,
borderWidth: isHovered ? 3 : 2, boxShadow: isHovered
color: isDark ? '#fff' : '#333', ? `0 8px 20px ${color}33`
fontSize: '13px', : `0 4px 12px ${color}1a`,
color: isDark ? '#fff' : '#1f1f1f',
fontSize: '14px',
fontWeight: 500, fontWeight: 500,
textAlign: 'center', textAlign: 'left',
cursor: 'pointer', cursor: 'pointer',
transition: 'all 0.2s ease', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
userSelect: 'none', userSelect: 'none',
filter: isHovered ? 'brightness(1.1)' : 'none', transform: isHovered ? 'translateY(-2px)' : 'translateY(0)',
boxShadow: isDark backdropFilter: 'blur(4px)',
? '0 2px 8px rgba(250, 173, 20, 0.2)'
: '0 2px 8px rgba(250, 173, 20, 0.15)',
}} }}
> >
{label} <div style={{ lineHeight: 1.4 }}>{label}</div>
<span <span
style={{ style={{
position: 'absolute', position: 'absolute',
top: -6, top: 10,
right: -6, right: 10,
padding: '2px 5px', padding: '2px 6px',
borderRadius: 4, borderRadius: 4,
background: 'linear-gradient(135deg, #faad14 0%, #ffc53d 100%)', background: 'rgba(250, 173, 20, 0.15)',
color: '#000', color: '#faad14',
fontSize: '9px', border: '1px solid rgba(250, 173, 20, 0.3)',
fontSize: '10px',
fontWeight: 700, fontWeight: 700,
letterSpacing: '0.5px', letterSpacing: '0.5px',
boxShadow: '0 1px 4px rgba(0,0,0,0.2)',
}} }}
> >
NEW NEW

View File

@@ -14,21 +14,19 @@ export const OriginalAttributeNode = memo(({ data }: OriginalAttributeNodeProps)
return ( return (
<div <div
style={{ style={{
padding: '6px 14px', padding: '8px 16px',
borderRadius: 6, borderRadius: 8,
background: isDark background: isDark
? `linear-gradient(135deg, ${color}33 0%, ${color}1a 100%)` ? `linear-gradient(135deg, ${color}22 0%, ${color}0a 100%)`
: `linear-gradient(135deg, ${color}22 0%, ${color}11 100%)`, : `linear-gradient(135deg, ${color}1a 0%, ${color}05 100%)`,
border: `2px solid ${color}`, border: `1px solid ${color}33`,
color: isDark ? 'rgba(255,255,255,0.95)' : 'rgba(0,0,0,0.85)', color: isDark ? 'rgba(255,255,255,0.9)' : 'rgba(0,0,0,0.85)',
fontSize: '13px', fontSize: '13px',
fontWeight: 500, fontWeight: 500,
textAlign: 'center', textAlign: 'center',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
userSelect: 'none', userSelect: 'none',
boxShadow: isDark boxShadow: '0 2px 6px rgba(0,0,0,0.05)',
? '0 2px 6px rgba(0,0,0,0.3)'
: '0 2px 6px rgba(0,0,0,0.1)',
}} }}
> >
{label} {label}

View File

@@ -45,7 +45,7 @@ export function useExpertTransformationLayout(
// Layout constants // Layout constants
const colStep = 140; const colStep = 140;
const categoryRowGap = 120; const categoryRowGap = 160; // increased gap
const minItemGap = 12; const minItemGap = 12;
const expertKeywordGap = 24; // gap between expert keywords const expertKeywordGap = 24; // gap between expert keywords
@@ -169,6 +169,31 @@ export function useExpertTransformationLayout(
// Category Y position (centered within its group) // Category Y position (centered within its group)
const categoryY = currentY + layout.totalHeight / 2 - 20; const categoryY = currentY + layout.totalHeight / 2 - 20;
// Add Group Node (Background)
const groupPaddingX = 60;
const groupPaddingY = 40;
const groupHeight = layout.totalHeight + (groupPaddingY * 2);
const groupWidth = descriptionX + 450; // Cover everything up to description end
nodes.push({
id: `group-${catIndex}`,
type: 'group',
position: {
x: categoryX - groupPaddingX,
y: currentY - groupPaddingY
},
data: {
label: result.category, // Add label to group
color: color,
width: groupWidth + groupPaddingX,
height: groupHeight,
isDark,
},
draggable: false,
selectable: false,
zIndex: -1,
});
// Add category node // Add category node
nodes.push({ nodes.push({
id: categoryId, id: categoryId,

View File

@@ -46,7 +46,7 @@ export function useTransformationLayout(
// Layout constants // Layout constants
const colStep = 140; const colStep = 140;
const categoryRowGap = 120; // large gap between different categories const categoryRowGap = 160; // increased gap between different categories
const minItemGap = 12; // minimum gap between transformation items const minItemGap = 12; // minimum gap between transformation items
const origAttrRowStep = 36; // step for original attributes (same visual rhythm) const origAttrRowStep = 36; // step for original attributes (same visual rhythm)
@@ -127,10 +127,11 @@ export function useTransformationLayout(
data: { data: {
label: data.query, label: data.query,
isDark, isDark,
fontSize, fontSize: fontSize + 2,
}, },
draggable: false, draggable: false,
selectable: false, selectable: false,
zIndex: 10,
}); });
// Track current Y position // Track current Y position
@@ -145,6 +146,31 @@ export function useTransformationLayout(
// Category Y position (centered within its group) // Category Y position (centered within its group)
const categoryY = currentY + layout.totalHeight / 2 - 20; const categoryY = currentY + layout.totalHeight / 2 - 20;
// Add Group Node (Background)
const groupPaddingX = 60;
const groupPaddingY = 40;
const groupHeight = layout.totalHeight + (groupPaddingY * 2);
const groupWidth = descriptionX + 450; // Cover everything up to description end
nodes.push({
id: `group-${catIndex}`,
type: 'group',
position: {
x: categoryX - groupPaddingX,
y: currentY - groupPaddingY
},
data: {
label: result.category, // Add label to group
color: color,
width: groupWidth + groupPaddingX,
height: groupHeight,
isDark,
},
draggable: false,
selectable: false,
zIndex: -1,
});
// Add category node // Add category node
nodes.push({ nodes.push({
id: categoryId, id: categoryId,
@@ -158,6 +184,7 @@ export function useTransformationLayout(
}, },
draggable: false, draggable: false,
selectable: false, selectable: false,
zIndex: 5,
}); });
// Edge from query to category // Edge from query to category
@@ -168,6 +195,9 @@ export function useTransformationLayout(
type: 'smoothstep', type: 'smoothstep',
style: { stroke: color, strokeWidth: 2, opacity: 0.6 }, style: { stroke: color, strokeWidth: 2, opacity: 0.6 },
animated: false, animated: false,
label: 'Classified as',
labelStyle: { fill: isDark ? '#aaa' : '#666', fontSize: 10 },
labelBgStyle: { fill: isDark ? '#1f1f1f' : '#fff', fillOpacity: 0.8 },
}); });
// Add original attribute nodes (distributed to match keyword spacing) // Add original attribute nodes (distributed to match keyword spacing)
@@ -186,6 +216,7 @@ export function useTransformationLayout(
}, },
draggable: false, draggable: false,
selectable: false, selectable: false,
zIndex: 5,
}); });
// Edge from category to original attribute // Edge from category to original attribute
@@ -216,6 +247,7 @@ export function useTransformationLayout(
}, },
draggable: false, draggable: false,
selectable: false, selectable: false,
zIndex: 5,
}); });
// Edge from category to keyword // Edge from category to keyword
@@ -231,6 +263,9 @@ export function useTransformationLayout(
strokeDasharray: '5,5', strokeDasharray: '5,5',
}, },
animated: true, animated: true,
label: 'Generates',
labelStyle: { fill: '#faad14', fontSize: 10, fontWeight: 600 },
labelBgStyle: { fill: isDark ? '#1f1f1f' : '#fff', fillOpacity: 0.8 },
}); });
// Find matching description // Find matching description
@@ -250,6 +285,7 @@ export function useTransformationLayout(
}, },
draggable: false, draggable: false,
selectable: false, selectable: false,
zIndex: 5,
}); });
// Edge from keyword to description // Edge from keyword to description
@@ -260,6 +296,9 @@ export function useTransformationLayout(
type: 'smoothstep', type: 'smoothstep',
style: { stroke: '#52c41a', strokeWidth: 2, opacity: 0.6 }, style: { stroke: '#52c41a', strokeWidth: 2, opacity: 0.6 },
animated: false, animated: false,
label: 'Describes',
labelStyle: { fill: '#52c41a', fontSize: 10, fontWeight: 600 },
labelBgStyle: { fill: isDark ? '#1f1f1f' : '#fff', fillOpacity: 0.8 },
}); });
} }
}); });
@@ -267,25 +306,7 @@ export function useTransformationLayout(
// Move Y position for next category // Move Y position for next category
currentY += layout.totalHeight; currentY += layout.totalHeight;
// Add divider line between categories (except after last one) // Add extra gap between categories
if (catIndex < data.results.length - 1) {
const dividerY = currentY + categoryRowGap / 2 - 1;
nodes.push({
id: `divider-${catIndex}`,
type: 'divider',
position: { x: queryX, y: dividerY },
data: {
isDark,
},
draggable: false,
selectable: false,
style: {
width: descriptionX + 400,
zIndex: -1,
},
});
}
currentY += categoryRowGap; currentY += categoryRowGap;
}); });

View File

@@ -24,7 +24,21 @@
display: none; display: none;
} }
/* Background pattern */ /* Background pattern */
.react-flow__background { .react-flow__background {
opacity: 0.5; opacity: 0.5;
} }
/* Group node backgrounds - ensure they render behind other nodes */
.react-flow__node-group {
z-index: -1 !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
}
.group-node-background {
pointer-events: none;
}