Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • graphpolaris/frontend-v2
  • rijkheere/frontend-v-2-reordering-paoh
2 results
Show changes
Showing
with 716 additions and 561 deletions
......@@ -4,7 +4,7 @@ import { toHandleId } from '..';
// Takes the querybuilder graph as an input and creates react flow elements for them.
export function createReactFlowElements<T extends Graph>(
graph: T
graph: T,
): {
nodes: Array<Node>;
edges: Array<Edge>;
......@@ -13,8 +13,6 @@ export function createReactFlowElements<T extends Graph>(
const edges: Array<Edge> = [];
graph.forEachNode((node, attributes): void => {
// console.log('attributes', attributes);
let position = { x: attributes?.x || 0, y: attributes?.y || 0 };
const RFNode: Node<typeof attributes> = {
id: node,
......@@ -28,8 +26,6 @@ export function createReactFlowElements<T extends Graph>(
// Add the reactflow edges
graph.forEachEdge((edge, attributes, source, target): void => {
// connection from attributes don't have visible connection lines
// if (attributes.type == 'attribute_connection') return;
const RFEdge: Edge<typeof attributes> = {
id: edge,
source: source,
......@@ -43,7 +39,5 @@ export function createReactFlowElements<T extends Graph>(
edges.push(RFEdge);
});
// console.log('nodes', nodes, 'edges', edges);
return { nodes, edges };
}
......@@ -195,6 +195,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
y: position.y - mouse_y,
name: dragData.name,
schemaKey: dragData.name,
attributes: [],
},
schema.getNodeAttribute(dragData.name, 'attributes'),
);
......@@ -212,6 +213,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
name: dragData.collection,
schemaKey: dragData.label,
collection: dragData.collection,
attributes: [],
},
schema.getEdgeAttribute(dragData.label, 'attributes'),
);
......@@ -238,6 +240,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
y: position.y,
name: fromNodeID,
schemaKey: fromNodeID,
attributes: [],
},
schema.getNodeAttribute(fromNodeID, 'attributes'),
);
......@@ -249,6 +252,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
y: position.y,
name: toNodeID,
schemaKey: toNodeID,
attributes: [],
},
schema.getNodeAttribute(toNodeID, 'attributes'),
);
......@@ -280,6 +284,8 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
x: position.x,
y: position.y,
logic: logic,
attributes: [],
inputs: {},
});
dispatch(setQuerybuilderGraphology(graphologyGraph));
......
......@@ -86,6 +86,8 @@ export const QueryBuilderLogicPillsPanel = (props: {
x: bounds.width / 2,
y: bounds.height / 2,
logic: logic,
attributes: [],
inputs: {},
});
} else {
const params = props.connection.params;
......@@ -97,6 +99,8 @@ export const QueryBuilderLogicPillsPanel = (props: {
x: position.x,
y: position.y,
logic: logic,
attributes: [],
inputs: {},
});
if (!logicNode?.id) throw new Error('Logic node has no id');
......
......@@ -91,6 +91,7 @@ export const QueryBuilderRelatedNodesPanel = (props: {
y: position.y,
name: entity.name,
schemaKey: entity.name,
attributes: [],
},
schemaGraph.getNodeAttribute(entity.name, 'attributes'),
);
......@@ -124,6 +125,7 @@ export const QueryBuilderRelatedNodesPanel = (props: {
depth: { min: queryBuilderSettings.depth.min, max: queryBuilderSettings.depth.max },
name: relation.collection,
collection: relation.collection,
attributes: [],
},
schemaGraph.getEdgeAttribute(relation.label, 'attributes'),
);
......
g {
g.react-flow__edge g {
@apply stroke-secondary-300;
}
......@@ -62,10 +62,6 @@ export const QueryRelationPill = memo((node: SchemaReactflowRelationNode) => {
// dispatch(setQuerybuilderGraphology(graphologyGraph));
// };
const calcWidth = (data: number) => {
return data.toString().length + 0.5 + 'ch';
};
return (
<div className="w-fit h-fit p-3 bg-transparent nowheel">
<RelationPill
......@@ -84,55 +80,6 @@ export const QueryRelationPill = memo((node: SchemaReactflowRelationNode) => {
}}
className={openDropdown ? 'border-secondary-200' : ''}
/>
{/* <span className="pr-1">
<span> [</span>
<input
className={
'bg-inherit text-center appearance-none mx-0.1 rounded-sm ' +
(depth.min < 0 || depth.min > depth.max ? ' bg-danger-400 ' : '')
}
style={{ maxWidth: calcWidth(depth.min) }}
type="number"
min={0}
placeholder={'?'}
value={depth.min}
onChange={(e) => {
setDepth({ ...depth, min: parseInt(e.target.value) });
}}
onBlur={(e) => {
onNodeUpdated();
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
onNodeUpdated();
}
}}
></input>
<span>..</span>
<input
className={
'bg-inherit text-center appearance-none mx-0.1 rounded-sm ' +
(depth.max > 99 || depth.min > depth.max ? ' bg-danger-400 ' : '')
}
style={{ maxWidth: calcWidth(depth.max) }}
type="number"
min={1}
placeholder={'?'}
value={depth.max}
onChange={(e) => {
setDepth({ ...depth, max: parseInt(e.target.value) });
}}
onBlur={(e) => {
onNodeUpdated();
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
onNodeUpdated();
}
}}
></input>
<span>]</span>
</span> */}
</div>
}
withHandles="horizontal"
......@@ -155,15 +102,13 @@ export const QueryRelationPill = memo((node: SchemaReactflowRelationNode) => {
></Handle>
}
>
{data?.attributes && (
<PillDropdown
node={node}
attributes={data.attributes}
attributeEdges={attributeEdges.map((edge) => edge?.attributes)}
open={openDropdown}
mr={-pillWidth * 0.05}
/>
)}
<PillDropdown
node={node}
attributes={data?.attributes || []}
attributeEdges={attributeEdges.map((edge) => edge?.attributes)}
open={openDropdown}
mr={-pillWidth * 0.05}
/>
</RelationPill>
</div>
);
......
import { useMemo, ReactElement, useState, useContext } from 'react';
import { NodeAttribute, QueryGraphEdges, SchemaReactflowEntityNode, handleDataFromReactflowToDataId, toHandleId } from '../../model';
import { Handle, Position, useUpdateNodeInternals } from 'reactflow';
import {
Handles,
NodeAttribute,
QueryElementTypes,
QueryGraphEdges,
SchemaReactflowEntityNode,
SchemaReactflowRelationNode,
} from '../../model';
import { Abc, CalendarToday, Map, Numbers, Place, QuestionMarkOutlined } from '@mui/icons-material';
import Icon from '@graphpolaris/shared/lib/components/icon';
import { PillHandle } from '@graphpolaris/shared/lib/components/pills/PillHandle';
import { pillDropdownPadding } from '@graphpolaris/shared/lib/components/pills/pill.const';
import { Button, TextInput, useAppDispatch, useQuerybuilderAttributesShown } from '../../..';
import { attributeShownToggle } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
import { isEqual } from 'lodash-es';
import { QueryBuilderDispatcherContext } from '../../panel/QueryBuilderDispatcher';
import { PillDropdownItem } from './PillDropdownItem';
type PillDropdownProps = {
node: SchemaReactflowEntityNode;
node: SchemaReactflowEntityNode | SchemaReactflowRelationNode;
attributes: NodeAttribute[];
attributeEdges: (QueryGraphEdges | undefined)[];
open: boolean;
......@@ -36,7 +40,6 @@ export const PillDropdown = (props: PillDropdownProps) => {
const [filter, setFilter] = useState<string>('');
const dispatch = useAppDispatch();
const attributesBeingShown = useQuerybuilderAttributesShown();
const { openLogicPillCreate } = useContext(QueryBuilderDispatcherContext);
const attributesOfInterest = useMemo(() => {
return props.attributes.map((attribute) =>
......@@ -48,58 +51,22 @@ export const PillDropdown = (props: PillDropdownProps) => {
<div className={'border-[1px] border-secondary-200 divide-y divide-secondary-200 !z-50'}>
{attributesOfInterest &&
attributesOfInterest.map((showing, i) => {
if (showing === false) return null;
const attribute = props.attributes[i];
if (attribute.handleData.attributeName === undefined) {
throw new Error('attribute.handleData.attributeName is undefined');
}
const handleId = toHandleId(handleDataFromReactflowToDataId(props.node, attribute));
const handleType = 'source';
if (!showing) return null;
return (
<div
className="px-2 py-1 bg-secondary-100 flex justify-between items-center"
key={(attribute.handleData.attributeName || '') + i}
>
<p className="truncate text-[0.6rem]">{attribute.handleData.attributeName}</p>
<Button
variantType="secondary"
variant="ghost"
size="2xs"
iconComponent={
attribute.handleData?.attributeDimension ? IconMap[attribute.handleData.attributeDimension] : <QuestionMarkOutlined />
}
onClick={() => {
openLogicPillCreate(
{
nodeId: props.node.id,
handleId: handleId,
handleType: handleType,
},
{
x: props.node.xPos + 200,
y: props.node.yPos + 50,
},
);
}}
/>
<PillHandle
mr={-pillDropdownPadding + (props.mr || 0)}
handleTop="auto"
position={Position.Right}
className={`stroke-white${props.className ? ` ${props.className}` : ''}`}
type="square"
>
<Handle
id={handleId}
type={handleType}
position={Position.Right}
className={'!rounded-none !bg-transparent !w-full !h-full !right-0 !left-0 !border-0'}
></Handle>
</PillHandle>
</div>
<PillDropdownItem
key={props.attributes[i].handleData.attributeName || i}
node={props.node}
attribute={props.attributes[i]}
mr={props.mr}
className={props.className}
icon={
props.attributes[i].handleData?.attributeDimension ? (
IconMap[props.attributes[i].handleData.attributeDimension || 0]
) : (
<QuestionMarkOutlined />
)
}
/>
);
})}
{(props.open || forceOpen) && (
......
import { ReactElement, useContext } from 'react';
import {
NodeAttribute,
SchemaReactflowEntityNode,
SchemaReactflowRelationNode,
handleDataFromReactflowToDataId,
toHandleId,
} from '../../model';
import { Handle, Position } from 'reactflow';
import { PillHandle } from '@graphpolaris/shared/lib/components/pills/PillHandle';
import { pillDropdownPadding } from '@graphpolaris/shared/lib/components/pills/pill.const';
import { Button } from '../../..';
import { QueryBuilderDispatcherContext } from '../../panel/QueryBuilderDispatcher';
type PillDropdownItemProps = {
attribute: NodeAttribute;
node: SchemaReactflowEntityNode | SchemaReactflowRelationNode;
className?: string;
mr?: number;
icon: ReactElement;
};
export const PillDropdownItem = (props: PillDropdownItemProps) => {
const { openLogicPillCreate } = useContext(QueryBuilderDispatcherContext);
if (props.attribute.handleData.attributeName === undefined) {
throw new Error('attribute.handleData.attributeName is undefined');
}
const handleId = toHandleId(handleDataFromReactflowToDataId(props.node, props.attribute));
const handleType = 'source';
return (
<div className="px-2 py-1 bg-secondary-100 flex justify-between items-center">
<p className="truncate text-[0.6rem]">{props.attribute.handleData.attributeName}</p>
<Button
variantType="secondary"
variant="ghost"
size="2xs"
iconComponent={props.icon}
onClick={() => {
openLogicPillCreate(
{
nodeId: props.node.id,
handleId: handleId,
handleType: handleType,
},
{
x: props.node.xPos + 200,
y: props.node.yPos + 50,
},
);
}}
/>
<PillHandle
mr={-pillDropdownPadding + (props.mr || 0)}
handleTop="auto"
position={Position.Right}
className={`stroke-white${props.className ? ` ${props.className}` : ''}`}
type="square"
>
<Handle
id={handleId}
type={handleType}
position={Position.Right}
className={'!rounded-none !bg-transparent !w-full !h-full !right-0 !left-0 !border-0'}
></Handle>
</PillHandle>
</div>
);
};
......@@ -14,6 +14,7 @@ const defaultQuery = {
query: [],
limit: 500,
return: ['*'],
machineLearning: [],
};
const defaultSettings: QueryBuilderSettings = {
......@@ -38,6 +39,7 @@ describe('QueryUtils Entity and Relations', () => {
x: 100,
y: 100,
name: 'Airport 1',
attributes: [],
});
const entity2 = graph.addPill2Graphology({
id: '10',
......@@ -45,6 +47,7 @@ describe('QueryUtils Entity and Relations', () => {
x: 200,
y: 200,
name: 'Airport 2',
attributes: [],
});
const relation1 = graph.addPill2Graphology({
......@@ -55,6 +58,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
graph.addEdge2Graphology(entity1, relation1);
......@@ -90,8 +94,8 @@ describe('QueryUtils Entity and Relations', () => {
it('should run multiple path query translation', () => {
const graph = new QueryMultiGraphology();
const e1 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e2 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 2' });
const e1 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
const e2 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 2', attributes: [] });
const r1 = graph.addPill2Graphology({
id: 'r1',
......@@ -101,13 +105,14 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
graph.addEdge2Graphology(e1, r1, { type: 'connection' });
graph.addEdge2Graphology(r1, e2, { type: 'connection' });
const e12 = graph.addPill2Graphology({ id: 'e12', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 12' });
const e22 = graph.addPill2Graphology({ id: 'e22', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 22' });
const e12 = graph.addPill2Graphology({ id: 'e12', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 12', attributes: [] });
const e22 = graph.addPill2Graphology({ id: 'e22', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 22', attributes: [] });
const r12 = graph.addPill2Graphology({
id: 'r12',
......@@ -117,6 +122,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports 2',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
graph.addEdge2Graphology(e12, r12);
......@@ -169,10 +175,10 @@ describe('QueryUtils Entity and Relations', () => {
it('should run one relation multiple entity query translation', () => {
const graph = new QueryMultiGraphology();
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 2' });
const e12 = graph.addPill2Graphology({ id: 'e12', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 12' });
const e22 = graph.addPill2Graphology({ id: 'e22', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 22' });
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 2', attributes: [] });
const e12 = graph.addPill2Graphology({ id: 'e12', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 12', attributes: [] });
const e22 = graph.addPill2Graphology({ id: 'e22', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 22', attributes: [] });
const r1 = graph.addPill2Graphology({
id: 'r1',
......@@ -182,6 +188,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
graph.addEdge2Graphology(e1, r1);
......@@ -235,10 +242,10 @@ describe('QueryUtils Entity and Relations', () => {
it('should run lone entities query translation', () => {
const graph = new QueryMultiGraphology();
const e1 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e2 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 2' });
const e12 = graph.addPill2Graphology({ id: 'e12', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 12' });
const e22 = graph.addPill2Graphology({ id: 'e22', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 22' });
const e1 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
const e2 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 2', attributes: [] });
const e12 = graph.addPill2Graphology({ id: 'e12', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 12', attributes: [] });
const e22 = graph.addPill2Graphology({ id: 'e22', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 22', attributes: [] });
const r1 = graph.addPill2Graphology({
id: 'r1',
......@@ -248,6 +255,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
graph.addEdge2Graphology(e1, r1);
......@@ -276,8 +284,8 @@ describe('QueryUtils Entity and Relations', () => {
it('should run lone relations query translation', () => {
const graph = new QueryMultiGraphology();
const e1 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e2 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 2' });
const e1 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
const e2 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 2', attributes: [] });
const r1 = graph.addPill2Graphology({
id: 'r1',
......@@ -287,6 +295,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
const r2 = graph.addPill2Graphology({
id: 'r2',
......@@ -296,6 +305,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports 2',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
graph.addEdge2Graphology(e1, r1);
......@@ -326,7 +336,7 @@ describe('QueryUtils Entity and Relations', () => {
it('should run relation only left side connected query translation', () => {
const graph = new QueryMultiGraphology();
const e1 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e1 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
const r1 = graph.addPill2Graphology({
id: 'r1',
......@@ -336,6 +346,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
graph.addEdge2Graphology(e1, r1);
......@@ -361,7 +372,7 @@ describe('QueryUtils Entity and Relations', () => {
it('should run relation only right side connected query translation', () => {
const graph = new QueryMultiGraphology();
const e2 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 2' });
const e2 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 2', attributes: [] });
const r1 = graph.addPill2Graphology({
id: 'r1',
......@@ -371,6 +382,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
graph.addEdge2Graphology(r1, e2);
......@@ -394,8 +406,8 @@ describe('QueryUtils Entity and Relations', () => {
it('should run entity and relations multi connection', () => {
const graph = new QueryMultiGraphology();
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 2' });
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 2', attributes: [] });
const r1 = graph.addPill2Graphology({
id: 'r1',
......@@ -405,6 +417,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
const r2 = graph.addPill2Graphology({
......@@ -415,6 +428,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports 2',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
graph.addEdge2Graphology(e1, r1);
......@@ -451,7 +465,7 @@ describe('QueryUtils Entity and Relations', () => {
it('should run in case of loops', () => {
const graph = new QueryMultiGraphology();
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
const r1 = graph.addPill2Graphology({
id: 'r1',
......@@ -461,6 +475,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
graph.addEdge2Graphology(e1, r1, { type: 'connection' });
......@@ -487,8 +502,8 @@ describe('QueryUtils Entity and Relations', () => {
it('should run in case of loops and regular', () => {
const graph = new QueryMultiGraphology();
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
const r1 = graph.addPill2Graphology({
id: 'r1',
......@@ -498,6 +513,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
graph.addEdge2Graphology(e1, r1, { type: 'connection' });
......@@ -538,8 +554,8 @@ describe('QueryUtils Entity and Relations', () => {
it('should run in case of loops and regular left', () => {
const graph = new QueryMultiGraphology();
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
const r1 = graph.addPill2Graphology({
id: 'r1',
......@@ -549,6 +565,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
graph.addEdge2Graphology(e1, r1, { type: 'connection' });
......@@ -584,8 +601,8 @@ describe('QueryUtils Entity and Relations', () => {
it('should run in case of direct entity connection', () => {
const graph = new QueryMultiGraphology();
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
graph.addEdge2Graphology(e1, e2, { type: 'connection' });
......@@ -618,6 +635,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
const r2 = graph.addPill2Graphology({
......@@ -628,6 +646,7 @@ describe('QueryUtils Entity and Relations', () => {
name: 'Flight between airports 2',
collection: 'Relation Pill',
depth: { min: 0, max: 1 },
attributes: [],
});
graph.addEdge2Graphology(r1, r2, { type: 'connection' });
......@@ -656,8 +675,8 @@ describe('QueryUtils Entity and Relations', () => {
it('should run in case of entity only loop', () => {
const graph = new QueryMultiGraphology();
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
graph.addEdge2Graphology(e1, e2, { type: 'connection' });
graph.addEdge2Graphology(e2, e1, { type: 'connection' });
......@@ -691,7 +710,7 @@ describe('QueryUtils Entity and Relations', () => {
it('should run in case of entity only self loop', () => {
const graph = new QueryMultiGraphology();
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] });
graph.addEdge2Graphology(e1, e1, { type: 'connection' });
......@@ -721,6 +740,7 @@ describe('QueryUtils calculateQueryLogic', () => {
x: 100,
y: 100,
name: 'Airport 1',
attributes: [],
},
[{ name: 'age', type: 'string' }],
);
......@@ -732,6 +752,8 @@ describe('QueryUtils calculateQueryLogic', () => {
y: 100,
name: 'Logic 1',
logic: MathFilters[NumberFilterTypes.EQUAL],
attributes: [],
inputs: {},
});
graph.addEdge2Graphology(e1, l1, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: '1' });
......@@ -754,6 +776,7 @@ describe('QueryUtils with Logic', () => {
x: 100,
y: 100,
name: 'Airport 1',
attributes: [],
},
[{ name: 'age', type: 'string' }],
);
......@@ -765,6 +788,8 @@ describe('QueryUtils with Logic', () => {
y: 100,
name: 'Logic 1',
logic: MathFilters[NumberFilterTypes.EQUAL],
attributes: [],
inputs: {},
});
graph.addEdge2Graphology(e1, l1, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: 'Value' });
......@@ -800,6 +825,7 @@ describe('QueryUtils with Logic', () => {
x: 100,
y: 100,
name: 'Airport 1',
attributes: [],
},
[{ name: 'age', type: 'string' }],
);
......@@ -811,6 +837,7 @@ describe('QueryUtils with Logic', () => {
x: 100,
y: 100,
name: 'Airport 2',
attributes: [],
},
[{ name: 'age', type: 'string' }],
);
......@@ -822,6 +849,8 @@ describe('QueryUtils with Logic', () => {
y: 100,
name: 'Filter EQ',
logic: MathFilters[NumberFilterTypes.EQUAL],
attributes: [],
inputs: {},
});
const l2 = graph.addLogicPill2Graphology({
......@@ -831,6 +860,8 @@ describe('QueryUtils with Logic', () => {
y: 100,
name: 'Logic ADD',
logic: NumberFunctions[NumberFunctionTypes.ADD],
attributes: [],
inputs: {},
});
graph.addEdge2Graphology(e1, l2, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: 'Value' });
......@@ -876,6 +907,7 @@ describe('QueryUtils with Logic', () => {
x: 100,
y: 100,
name: 'Airport 1',
attributes: [],
},
[{ name: 'age', type: 'string' }],
);
......@@ -887,6 +919,8 @@ describe('QueryUtils with Logic', () => {
y: 100,
name: 'Logic LT',
logic: MathFilters[NumberFilterTypes.LESS_THAN],
attributes: [],
inputs: {},
});
const l2 = graph.addLogicPill2Graphology({
......@@ -896,6 +930,8 @@ describe('QueryUtils with Logic', () => {
y: 100,
name: 'Logic average',
logic: MathAggregations[NumberAggregationTypes.AVG],
attributes: [],
inputs: {},
});
graph.addEdge2Graphology(e1, l2, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: 'Value' });
......@@ -932,6 +968,7 @@ describe('QueryUtils with Logic', () => {
x: 100,
y: 100,
name: 'Airport 1',
attributes: [],
},
[{ name: 'age', type: 'string' }],
);
......@@ -944,6 +981,8 @@ describe('QueryUtils with Logic', () => {
y: 100,
name: 'Logic LT',
logic: MathFilters[NumberFilterTypes.LESS_THAN],
attributes: [],
inputs: {},
},
{ '1': 5 },
);
......@@ -982,6 +1021,7 @@ it('should no connections between entities and relations', () => {
x: 100,
y: 100,
name: 'Airport 1',
attributes: [],
},
[{ name: 'age', type: 'string' }],
);
......@@ -993,6 +1033,7 @@ it('should no connections between entities and relations', () => {
x: 100,
y: 100,
name: 'Airport 2',
attributes: [],
},
[{ name: 'age', type: 'string' }],
);
......@@ -1004,6 +1045,8 @@ it('should no connections between entities and relations', () => {
y: 100,
name: 'Relation 1',
depth: { min: 0, max: 1 },
attributes: [],
collection: 'r',
},
[{ name: 'age', type: 'string' }],
);
......
......@@ -31,7 +31,7 @@ const traverseEntityRelationPaths = (
// console.log('new path');
paths.push([]);
if (node.attributes.type === QueryElementTypes.Relation) {
paths[currentIdx].push({ type: QueryElementTypes.Entity, x: node.attributes.x, y: node.attributes.y });
paths[currentIdx].push({ type: QueryElementTypes.Entity, x: node.attributes.x, y: node.attributes.y, attributes: [] });
}
} else if (paths[currentIdx].length > 0) {
const lastNode = paths[currentIdx][paths[currentIdx].length - 1];
......@@ -39,13 +39,15 @@ const traverseEntityRelationPaths = (
if (lastNode.type === QueryElementTypes.Entity) {
paths[currentIdx].push({
type: QueryElementTypes.Relation,
collection: node.key,
x: node.attributes.x,
y: node.attributes.x,
depth: { min: settings.depth.min, max: settings.depth.max },
direction: 'both',
attributes: [],
});
} else {
paths[currentIdx].push({ type: QueryElementTypes.Entity, x: node.attributes.x, y: node.attributes.x });
paths[currentIdx].push({ type: QueryElementTypes.Entity, x: node.attributes.x, y: node.attributes.x, attributes: [] });
}
}
}
......@@ -59,7 +61,7 @@ const traverseEntityRelationPaths = (
let connections = edges.filter((e) => e.source === node.key);
if (connections.length === 0) {
if (node.attributes.type === QueryElementTypes.Relation) {
paths[currentIdx].push({ type: QueryElementTypes.Entity, x: node.attributes.x, y: node.attributes.x });
paths[currentIdx].push({ type: QueryElementTypes.Entity, x: node.attributes.x, y: node.attributes.x, attributes: [] });
}
return 0;
}
......@@ -139,6 +141,9 @@ export function calculateQueryLogic(
if (!connectionToInputRef.attributes?.sourceHandleData)
throw Error('Malformed Graph! Logic node is connected but has no sourceHandleData');
// Is connected to entity or relation node
if (connectionToInputRef.attributes.sourceHandleData.attributeName === '(# Connection)') {
return ['Count', `@${connectionToInputRef.attributes.sourceHandleData.nodeId}`];
}
return `@${connectionToInputRef.attributes.sourceHandleData.nodeId}.${connectionToInputRef.attributes.sourceHandleData.attributeName}`;
}
} else {
......
......@@ -108,7 +108,8 @@ export const VisualizationPanel = ({ fullSize }: { fullSize: () => void }) => {
{!!viz &&
activeVisualizationIndex !== -1 &&
openVisualizationArray?.[activeVisualizationIndex] &&
viz.id === openVisualizationArray[activeVisualizationIndex].id && (
viz.id === openVisualizationArray[activeVisualizationIndex].id &&
graphMetadata && (
<viz.component
data={graphQueryResult}
schema={schema}
......
import React from 'react';
import { SelectionStateI, unSelect } from '@graphpolaris/shared/lib/data-access/store/interactionSlice';
import { Delete } from '@mui/icons-material';
import { useDispatch } from 'react-redux';
......@@ -25,8 +26,8 @@ export const SelectionConfig = () => {
/>
</div>
{selection.content.map((item, index) => (
<>
<div key={index + 'id'} className="flex justify-between items-center px-4 py-1 gap-1">
<React.Fragment key={index + 'id'}>
<div className="flex justify-between items-center px-4 py-1 gap-1">
<span className="text-xs font-normal">ID</span>
<span className="text-xs">{item._id}</span>
</div>
......@@ -43,7 +44,7 @@ export const SelectionConfig = () => {
</div>
);
})}
</>
</React.Fragment>
))}
</div>
);
......
......@@ -102,7 +102,7 @@ export function VisualizationSettings({}: Props) {
label="Name"
inline
/>
{activeVisualization && (
{activeVisualization && graphMetadata && (
<>
<SettingsHeader name="Visualization Settings" />
<Suspense fallback={<div>Loading...</div>}>
......
.matrix {
--size: 50px;
}
.axisLeft, .axisTop {
position: absolute;
left: 0;
top: 0;
backdrop-filter: blur(10px);
background: rgba(255,255,255, 0.5);
}
.axisLeft {
top: var(--size);
bottom: 0;
width: var(--size);
box-shadow: 1px 0px 0px 0px rgba(0,0,0,0.2);
}
.axisTop {
right: 0;
height: var(--size);
box-shadow: var(--size) 1px 0px 0px rgba(0,0,0,0.2);
}
/* mask top left corner */
.axisTop + svg {
clip-path: polygon(
0% 0%,
0% 100%,
100% 100%,
100% 0%,
var(--size) 0%,
var(--size) var(--size),
0% var(--size)
);
}
\ No newline at end of file
......@@ -6,18 +6,18 @@ export function processLinkPrediction(ml: ML, graph: GraphType): GraphType {
if (ml === undefined || ml.linkPrediction === undefined) return graph;
if (ml.linkPrediction.enabled) {
let allNodeIds = new Set(graph.nodes.map((n) => n._id));
let allNodeIds = new Set(Object.keys(graph.nodes));
ml.linkPrediction.result.forEach((link) => {
if (allNodeIds.has(link.from) && allNodeIds.has(link.to)) {
const toAdd: LinkType = {
id: link.from + link.to,
id: link.from + ':LP:' + link.to, // TODO: this only supports one link between two nodes
source: link.from,
target: link.to,
value: link.attributes.jaccard_coefficient as number,
mlEdge: true,
color: 0x000000,
};
graph.links.push(toAdd);
graph.links[toAdd.id] = toAdd;
}
});
}
......@@ -35,18 +35,16 @@ export function processCommunityDetection(ml: ML, graph: GraphType): GraphType {
});
});
graph.nodes = graph.nodes.map((node, i) => {
if (allNodeIdMap.has(node._id)) {
node.cluster = allNodeIdMap.get(node._id);
Object.keys(graph.nodes).forEach((nodeId) => {
if (allNodeIdMap.has(nodeId)) {
graph.nodes[nodeId].cluster = allNodeIdMap.get(nodeId);
} else {
node.cluster = -1;
graph.nodes[nodeId].cluster = -1;
}
return node;
});
} else {
graph.nodes = graph.nodes.map((node, i) => {
node.cluster = undefined;
return node;
Object.keys(graph.nodes).forEach((nodeId) => {
graph.nodes[nodeId].cluster = undefined;
});
}
return graph;
......@@ -101,6 +99,7 @@ export const useNLMachineLearning = (props: {
* Gets the edges corresponding to the shortestPath.
* @param pathString The path as a string.
* @returns The path as a LinkType[]
* @deprecated This function is not working anymore
*/
function getShortestPathEdges(pathString: string[]): LinkType[] {
try {
......@@ -112,13 +111,14 @@ export const useNLMachineLearning = (props: {
continue;
}
let edgeFound = false;
props.graph.links.forEach((link: any) => {
const { source, target } = link;
Object.keys(props.graph.links).forEach((key) => {
const link = props.graph.links[key];
if (
(pathString[index] == source.id && pathString[index + 1] == target.id) ||
(pathString[index] == source && pathString[index + 1] == target) ||
(pathString[index + 1] == source.id && pathString[index] == target.id) ||
(pathString[index + 1] == source && pathString[index] == target)
false // FIXME: This is not working anymore
// (pathString[index] == source.id && pathString[index + 1] == target.id) ||
// (pathString[index] == source && pathString[index + 1] == target) ||
// (pathString[index + 1] == source.id && pathString[index] == target.id) ||
// (pathString[index + 1] == source && pathString[index] == target)
) {
newPath.push(link);
edgeFound = true;
......@@ -172,10 +172,10 @@ export const useNLMachineLearning = (props: {
* after a community detection algorithm, where the cluster of these nodes could have been changed.
*/
const resetClusterOfNodes = (type: number): void => {
props.graph.nodes.forEach((node: NodeType) => {
const numberOfClusters = props.numberOfMlClusters;
Object.keys(props.graph.nodes).forEach((key) => {
const node = props.graph.nodes[key];
if (node.cluster == type) {
node.cluster = numberOfClusters;
node.cluster = props.numberOfMlClusters;
}
if (node.type == type) {
node.cluster = node.type;
......
......@@ -11,9 +11,7 @@ export const NLPopup = (props: NodelinkPopupProps) => {
return (
<div
className="absolute card card-bordered z-50 bg-light rounded-none text-[0.9rem] min-w-[10rem] pointer-events-none"
// style={{ top: props.data.pos.y + 10, left: props.data.pos.x + 10 }}
style={{ transform: 'translate(' + (props.data.pos.x + 20) + 'px, ' + (props.data.pos.y + 10) + 'px)' }}
className="text-[0.9rem] min-w-[10rem]"
>
<div className="card-body p-0">
<span className="px-2.5 pt-2">
......
......@@ -142,7 +142,11 @@ type OptionsI = {
* @returns {GraphType} A node-link graph containing the nodes and links for the diagram.
*/
export function parseQueryResult(queryResult: GraphQueryResult, ml: ML, options: OptionsI = {}): GraphType {
const nodes: NodeType[] = [];
let ret: GraphType = {
nodes: {},
links: {},
};
const typeDict: { [key: string]: number } = {};
// Counter for the types
let counter = 1;
......@@ -184,8 +188,8 @@ export function parseQueryResult(queryResult: GraphQueryResult, ml: ML, options:
type: typeNumber,
displayInfo: preferredText,
radius: radius,
x: (options.defaultX || 0) + Math.random() * radius * 20 - radius * 10,
y: (options.defaultY || 0) + Math.random() * radius * 20 - radius * 10,
defaultX: (options.defaultX || 0) + Math.random() * radius * 20 - radius * 10,
defaultY: (options.defaultY || 0) + Math.random() * radius * 20 - radius * 10,
};
// let mlExtra = {};
......@@ -209,13 +213,13 @@ export function parseQueryResult(queryResult: GraphQueryResult, ml: ML, options:
// Add mlExtra to the node if necessary
// data = { ...data, ...mlExtra };
nodes.push(data);
ret.nodes[data._id] = data;
}
// Filter unique edges and transform to LinkTypes
// List for all links
let links: LinkType[] = [];
let allNodeIds = new Set(nodes.map((n) => n._id));
let allNodeIds = new Set(Object.keys(ret.nodes));
// Parse ml edges
// if (ml != undefined) {
......@@ -239,14 +243,14 @@ export function parseQueryResult(queryResult: GraphQueryResult, ml: ML, options:
for (let i = 0; i < uniqueEdges.length; i++) {
if (allNodeIds.has(uniqueEdges[i].from) && allNodeIds.has(uniqueEdges[i].to)) {
const toAdd: LinkType = {
id: uniqueEdges[i].from + ':' + uniqueEdges[i].to,
id: uniqueEdges[i].from + ':' + uniqueEdges[i].to, // TODO: this only supports one link between two nodes
source: uniqueEdges[i].from,
target: uniqueEdges[i].to,
value: uniqueEdges[i].count,
mlEdge: false,
color: 0x000000,
};
links.push(toAdd);
ret.links[toAdd.id] = toAdd;
}
}
......@@ -266,13 +270,13 @@ export function parseQueryResult(queryResult: GraphQueryResult, ml: ML, options:
}
// Graph to be returned
let toBeReturned: GraphType = {
nodes: nodes,
links: links,
// linkPrediction: linkPredictionInResult,
// shortestPath: shortestPathInResult,
// communityDetection: communityDetectionInResult,
};
// let toBeReturned: GraphType = {
// nodes: nodes,
// links: links,
// linkPrediction: linkPredictionInResult,
// shortestPath: shortestPathInResult,
// communityDetection: communityDetectionInResult,
// };
// If query with community detection; add number of clusters to the graph
// const numberOfClusters = {
......@@ -283,5 +287,5 @@ export function parseQueryResult(queryResult: GraphQueryResult, ml: ML, options:
// }
// return toBeReturned;
return processML(ml, toBeReturned);
return processML(ml, ret);
}