diff --git a/apps/web/src/components/navbar/DatabaseManagement/forms/settings.tsx b/apps/web/src/components/navbar/DatabaseManagement/forms/settings.tsx index 1c761a6a293617198708186c3b2b5b7b5b294722..fb0fcb6a919311c900b1ba40e13b5cac290330d6 100644 --- a/apps/web/src/components/navbar/DatabaseManagement/forms/settings.tsx +++ b/apps/web/src/components/navbar/DatabaseManagement/forms/settings.tsx @@ -172,7 +172,7 @@ export const SettingsForm = (props: { onClose(): void; open: 'add' | 'update'; s <Button variantType="primary" className="flex-grow" - label={connection.updating ? formTitle.slice(0, -1) + 'ing...' : formTitle} + label={connection.updating ? (formTitle === 'Add' ? formTitle + 'ing...' : formTitle.slice(0, -1) + 'ing...') : formTitle} onClick={(event) => { event.preventDefault(); handleSubmit(); diff --git a/libs/shared/lib/querybuilder/model/graphology/metaAttributes.ts b/libs/shared/lib/querybuilder/model/graphology/metaAttributes.ts new file mode 100644 index 0000000000000000000000000000000000000000..4c34d0884ba11b1602fcb13596f35e0b1cdcee00 --- /dev/null +++ b/libs/shared/lib/querybuilder/model/graphology/metaAttributes.ts @@ -0,0 +1,28 @@ +import { SchemaAttribute } from '../../..'; +import { Handles, QueryElementTypes } from '../reactflow'; +import { QueryGraphEdgeAttribute, QueryGraphEdgeHandle, QueryGraphNodes } from './model'; + +const metaAttribute: Record<string, QueryGraphEdgeAttribute> = { + '(# Connection)': { + attributeName: '(# Connection)', + attributeType: 'float', + attributeDimension: 'numerical', + }, +}; + +export function checkForMetaAttributes(graphologyAttributes: QueryGraphNodes): QueryGraphEdgeHandle[] { + const ret: QueryGraphEdgeHandle[] = []; + const defaultHandleData = { + nodeId: graphologyAttributes.id, + nodeName: graphologyAttributes.name || '', + nodeType: graphologyAttributes.type, + handleType: graphologyAttributes.type === QueryElementTypes.Entity ? Handles.EntityAttribute : Handles.RelationAttribute, + }; + + // Only include if not already there + const metaAttributesToInclude = Object.keys(metaAttribute).filter((attributeName) => !(attributeName in graphologyAttributes.attributes)); + return metaAttributesToInclude.map((attributeName) => ({ + ...defaultHandleData, + ...metaAttribute[attributeName], + })) as QueryGraphEdgeHandle[]; +} diff --git a/libs/shared/lib/querybuilder/model/graphology/model.ts b/libs/shared/lib/querybuilder/model/graphology/model.ts index dc4a2bc1a42ebfa4cc9a2f823458dd3fa4a1cd42..b47deeb6e08999538cef80e4c2d163a78125eda8 100644 --- a/libs/shared/lib/querybuilder/model/graphology/model.ts +++ b/libs/shared/lib/querybuilder/model/graphology/model.ts @@ -23,7 +23,7 @@ export type NodeDefaults = { type: QueryElementTypes; width?: number; height?: number; - attributes?: NodeAttribute[]; + attributes: NodeAttribute[]; selected?: boolean; }; @@ -33,6 +33,7 @@ export interface EntityData { leftRelationHandleId?: QueryGraphEdgeHandle; rightRelationHandleId?: QueryGraphEdgeHandle; selected?: boolean; + type: QueryElementTypes.Entity; } /** Interface for the data in an relation node. */ @@ -44,6 +45,7 @@ export interface RelationData { rightEntityHandleId?: QueryGraphEdgeHandle; direction?: 'left' | 'right' | 'both'; selected?: boolean; + type: QueryElementTypes.Relation; } export interface LogicData { @@ -53,18 +55,19 @@ export interface LogicData { // key: string; logic: GeneralDescription<AllLogicTypes>; inputs: Record<string, InputNodeTypeTypes>; // name from InputNode -> InputNodeTypeTypes + type: QueryElementTypes.Logic; } -export type EntityNodeAttributes = XYPosition & EntityData & NodeDefaults; -export type RelationNodeAttributes = XYPosition & RelationData & NodeDefaults; -export type LogicNodeAttributes = XYPosition & LogicData & NodeDefaults; +export type EntityNodeAttributes = XYPosition & NodeDefaults & EntityData; +export type RelationNodeAttributes = XYPosition & NodeDefaults & RelationData; +export type LogicNodeAttributes = XYPosition & NodeDefaults & LogicData; export type QueryGraphNodes = EntityNodeAttributes | RelationNodeAttributes | LogicNodeAttributes; export type QueryGraphEdgeAttribute = { - attributeName?: string; - attributeType?: InputNodeType; - attributeDimension?: InputNodeDimension; + attributeName: string; + attributeType: InputNodeType; + attributeDimension: InputNodeDimension; }; export type QueryGraphEdgeHandle = { @@ -72,7 +75,7 @@ export type QueryGraphEdgeHandle = { nodeName: string; nodeType: QueryElementTypes; handleType: Handles; -} & QueryGraphEdgeAttribute; +} & Partial<QueryGraphEdgeAttribute>; export type QueryGraphEdges = { type: string; @@ -80,10 +83,6 @@ export type QueryGraphEdges = { targetHandleData: QueryGraphEdgeHandle; }; -export type QueryGraphEdgesOpt = { - type?: string; - sourceHandleData?: QueryGraphEdgeHandle; - targetHandleData?: QueryGraphEdgeHandle; -}; +export type QueryGraphEdgesOpt = Partial<QueryGraphEdges>; // export class QueryGraph extends Graph<QueryGraphNodes, GAttributes, GAttributes>; // is in utils.ts diff --git a/libs/shared/lib/querybuilder/model/graphology/utils.ts b/libs/shared/lib/querybuilder/model/graphology/utils.ts index 041fe0cde310d5a3a9ab8b2b9056c66d86f14015..f08865788e95edfd3ffd42c6ae8a4002f2e4dae2 100644 --- a/libs/shared/lib/querybuilder/model/graphology/utils.ts +++ b/libs/shared/lib/querybuilder/model/graphology/utils.ts @@ -4,7 +4,6 @@ import { Attributes as GAttributes, Attributes, SerializedGraph } from 'grapholo import { EntityNodeAttributes, LogicNodeAttributes, - QueryGraphEdgeAttribute, QueryGraphEdgeHandle, QueryGraphEdges, QueryGraphEdgesOpt, @@ -15,6 +14,7 @@ import { XYPosition } from 'reactflow'; import { Handles, QueryElementTypes } from '../reactflow'; import { SchemaAttribute, SchemaAttributeTypes } from '@graphpolaris/shared/lib/schema'; import { InputNodeType, InputNodeTypeTypes } from '../logic/general'; +import { checkForMetaAttributes } from './metaAttributes'; /** monospace fontsize table */ const widthPerFontsize = { @@ -54,6 +54,9 @@ export class QueryMultiGraphology extends Graph<QueryGraphNodes, QueryGraphEdges if (!attributes.id) attributes.id = 'id_' + (Date.now() + Math.floor(Math.random() * 1000)).toString(); + // Add to the beginning the meta attributes, such as (# Connection) + attributes.attributes = [...checkForMetaAttributes(attributes).map((a) => ({ handleData: a })), ...attributes.attributes]; + return attributes; } @@ -113,19 +116,17 @@ export class QueryMultiGraphology extends Graph<QueryGraphNodes, QueryGraphEdges return attributes; } - public addLogicPill2Graphology(attributes: QueryGraphNodes, inputValues: Record<string, InputNodeTypeTypes> = {}): QueryGraphNodes { - attributes = this.configureDefaults(attributes); - if (!attributes.type) attributes.type = QueryElementTypes.Logic; + public addLogicPill2Graphology(attributes: LogicNodeAttributes, inputValues: Record<string, InputNodeTypeTypes> = {}): QueryGraphNodes { + attributes = this.configureDefaults(attributes) as LogicNodeAttributes; if (!attributes.name || !attributes.id) throw Error('type or name is not defined'); // add default inputs, but only if not there yet if (attributes.type === QueryElementTypes.Logic) { - if ((attributes as LogicNodeAttributes).inputs === undefined) { - attributes = attributes as LogicNodeAttributes; - (attributes as LogicNodeAttributes).logic.inputs.forEach((input, i) => { + if (attributes.inputs === undefined || Object.keys(attributes.inputs).length === 0) { + attributes.logic.inputs.forEach((input, i) => { // Setup default non-linked inputs as regular values matching the input expected type - if (!(attributes as LogicNodeAttributes).inputs) (attributes as LogicNodeAttributes).inputs = {}; - (attributes as LogicNodeAttributes).inputs[input.name] = inputValues?.[input.name] || input.default; + if (!attributes.inputs) attributes.inputs = {}; + attributes.inputs[input.name] = inputValues?.[input.name] || input.default; }); // (attributes as LogicNodeAttributes).leftEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationLeft, ''); // (attributes as LogicNodeAttributes).rightEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationRight, ''); diff --git a/libs/shared/lib/querybuilder/model/reactflow/utils.ts b/libs/shared/lib/querybuilder/model/reactflow/utils.ts index b8a0dd7913527737b527ede89bd5d96d80c884cd..7ee9c55c078ee19fdc957b43d1efa4c220f1c756 100644 --- a/libs/shared/lib/querybuilder/model/reactflow/utils.ts +++ b/libs/shared/lib/querybuilder/model/reactflow/utils.ts @@ -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 }; } diff --git a/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx b/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx index 26da1edfb41b03e3e960c6d7ee7e1acf8b5028fc..a54e5e213aba9371059578c3a8d60269755d8d8f 100644 --- a/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx +++ b/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx @@ -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)); diff --git a/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderLogicPillsPanel.tsx b/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderLogicPillsPanel.tsx index 041acb8dfad4987c3d2863f57333983d0243ecde..0b9f7cff881741335e9413acaf28882b0b38eba6 100644 --- a/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderLogicPillsPanel.tsx +++ b/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderLogicPillsPanel.tsx @@ -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'); diff --git a/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderRelatedNodesPanel.tsx b/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderRelatedNodesPanel.tsx index 3b57002c3b1a5fc19656a596182ecd959f5e6425..5b398a8d50a65ec8cc45697e1bd2dd78950dbdac 100644 --- a/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderRelatedNodesPanel.tsx +++ b/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderRelatedNodesPanel.tsx @@ -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'), ); diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx index 3bb9e75d3b2a75f6547594fc47946df45407dfb7..1fd01d879868d91cffd1051af362c58e97eea402 100644 --- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx @@ -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> ); diff --git a/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx b/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx index 07caaabbf76bed3384b3992afe6a4a15f0102242..0868585845f4df0ac04de36f8d7fe5887fab34c5 100644 --- a/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx +++ b/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx @@ -1,17 +1,21 @@ 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) && ( diff --git a/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdownItem.tsx b/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdownItem.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6304229d14cbdff0438f6d2b60c623002456171a --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdownItem.tsx @@ -0,0 +1,71 @@ +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> + ); +}; diff --git a/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts b/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts index a2ed912b992d56b664adc30a5bf22b9dda77477a..8b35dba9498b26054d57f7706d4792ca838a1693 100644 --- a/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts +++ b/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts @@ -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' }], ); diff --git a/libs/shared/lib/querybuilder/query-utils/query2backend.ts b/libs/shared/lib/querybuilder/query-utils/query2backend.ts index 92fd9e6f2c4a21c6150f05f6f1969adbcb88db89..f04b9f1cf72eddc9e47b1308d97d007400dcf044 100644 --- a/libs/shared/lib/querybuilder/query-utils/query2backend.ts +++ b/libs/shared/lib/querybuilder/query-utils/query2backend.ts @@ -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 { diff --git a/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx b/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx index 864c40a546ab428b6e0172f4bf321b2afe790880..83223b9829e834d24c62b5fb3ce2efd03c7cbdf7 100644 --- a/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx +++ b/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx @@ -53,7 +53,7 @@ export const NLPixi = (props: Props) => { const app = useMemo( () => new Application({ - background: 0xffffff, + backgroundAlpha: 0, antialias: true, autoDensity: true, eventMode: 'auto',