Better Style Designed by Gemini
This commit is contained in:
@@ -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';
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
57
frontend/src/components/dag/nodes/GroupNode.tsx
Normal file
57
frontend/src/components/dag/nodes/GroupNode.tsx
Normal 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';
|
||||||
@@ -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,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user