diff --git a/libs/shared/lib/vis/visualizations/paohvis/paohvis.tsx b/libs/shared/lib/vis/visualizations/paohvis/paohvis.tsx index d242ccc1a067dff0b74a3374a6c5e920fc1776fa..0e321f471415a313f279c80cb814b60ede24f2ec 100644 --- a/libs/shared/lib/vis/visualizations/paohvis/paohvis.tsx +++ b/libs/shared/lib/vis/visualizations/paohvis/paohvis.tsx @@ -233,11 +233,8 @@ export const PaohVis = ({ data, graphMetadata, schema, configuration: conf, upda const index = className.split('circle-')[1]; if (currentPageRows) { const indexNumber = parseInt(index); - const { pageRow } = currentPageRows; - if (typeof pageRow === 'number') { - const rowSelector = `.row-${indexNumber - (pageRow - 1) * configPaohvis.rowsMaxPerPage}`; - select(rowSelector).selectAll('text').attr('fill', configStyle.colorText); - } + const rowSelector = `.row-${indexNumber - currentPageRows.startIndexRow}`; + select(rowSelector).selectAll('text').attr('fill', configStyle.colorText); } }); }; diff --git a/libs/shared/lib/vis/visualizations/paohvis/types.ts b/libs/shared/lib/vis/visualizations/paohvis/types.ts index 80692ced94a5da62379bca8d66e10ff6b0d433a5..0d28f205c1ca7528b3600b4f2a887ea8c4b53f76 100644 --- a/libs/shared/lib/vis/visualizations/paohvis/types.ts +++ b/libs/shared/lib/vis/visualizations/paohvis/types.ts @@ -49,6 +49,7 @@ export type HyperEdgeRange2 = { rangeText: any; hyperEdges: HyperEdgeI2; _id: string; + reduced_ids: string[]; degree: number; }; diff --git a/libs/shared/lib/vis/visualizations/paohvis/utils/ToPaohvisDataParserUsecase.tsx b/libs/shared/lib/vis/visualizations/paohvis/utils/ToPaohvisDataParserUsecase.tsx index ce9c2ecbfd09fbd37661c0646802986aff6e5f99..908360622329e646eb0636a7ffb5578a197cce74 100644 --- a/libs/shared/lib/vis/visualizations/paohvis/utils/ToPaohvisDataParserUsecase.tsx +++ b/libs/shared/lib/vis/visualizations/paohvis/utils/ToPaohvisDataParserUsecase.tsx @@ -6,239 +6,10 @@ import { getGroupName } from './ResultNodeLinkParserUseCase'; import { GraphQueryResult, Node } from '@graphpolaris/shared/lib/data-access'; -import { MultiGraph } from 'graphology'; -import { - PaohvisAxisInfo, - PaohvisData, - PaohvisFilters, - PaohvisNodeInfo, - PaohvisNodeOrder, - Relation, - AugmentedNodeAttributesGraph, - HyperEdgeRange2, - HyperEdgeI2, -} from '../types'; +import { PaohvisAxisInfo, PaohvisData, PaohvisFilters, PaohvisNodeInfo, HyperEdgeRange2, HyperEdgeI2 } from '../types'; import AttributeFilterUsecase, { getIds } from './AttributesFilterUseCase'; -//import SortUseCase from './SortUseCase'; -import { - sortObjectsByDegree, - countRepetition, - getTransformIndices, - sortObjectsByName2Show, - sortByIndicesLength2, - sortByRangeText2, -} from './utils'; +import { countRepetition } from './utils'; //type Index = number; //type Index = number; - -/** - * This parser is used to parse the incoming query result to the format that's needed to make the Paohvis table. - */ -export class ToPaohvisDataParserUseCase { - private queryResult: GraphQueryResult; - private xAxisNodeGroup: string; - private yAxisNodeGroup: string; - private paohvisFilters: PaohvisFilters; - - public constructor(queryResult: GraphQueryResult) { - this.queryResult = queryResult; - - this.xAxisNodeGroup = ''; - this.yAxisNodeGroup = ''; - this.paohvisFilters = { nodeFilters: [], edgeFilters: [] }; - } - /** - * Parses query results to the format that's needed to make a Paohvis table. - * @param axisInfo is the information that's needed to parse everything to the correct axis. - * @param nodeOrder is the order in which the nodes should be parsed. - * @returns the information that's needed to make a Paohvis table. - */ - public parseQueryResult(axisInfo: PaohvisAxisInfo, entityHorizontal: string, entityVertical: string): PaohvisData { - console.log('this.paohvisFilters ', this.paohvisFilters); - - this.setAxesNodeGroups(axisInfo, entityHorizontal, entityVertical); - - const augmentedNodes: AugmentedNodeAttributesGraph[] = this.queryResult.nodes.map((node: Node) => ({ - id: node._id, - attributes: node.attributes, - label: node._id, - })); - - //const nodeA: AugmentedNodeAttributesGraph[] = data.nodes.filter((obj) => obj.id.includes(configuration.rowNode)); - - console.log('augmentedNodes ', this.queryResult.nodes); - - const nodesRow: AugmentedNodeAttributesGraph[] = augmentedNodes.filter((node) => { - return node.label == this.yAxisNodeGroup; - }); - const nodesColumn: AugmentedNodeAttributesGraph[] = augmentedNodes.filter((node) => { - return node.label == this.xAxisNodeGroup; - }); - - const nodeIdIndex: { [id: string]: number } = nodesRow.reduce((acc, node, index) => ({ ...acc, [node._id]: index }), {}); - - const filteredData = AttributeFilterUsecase.applyFilters(this.queryResult, this.paohvisFilters); - - //parse nodes - const rowInfo: PaohvisNodeInfo = ToPaohvisDataParserUseCase.parseNodes(nodesRow, this.yAxisNodeGroup, this.xAxisNodeGroup); - - let nodeListAttr: any[] = []; - nodeListAttr = nodesRow.map((node) => node._id); - nodeListAttr = nodesRow.map((node) => node._id); - - const [resultHyperEdgeRanges, rowsDegree] = ToPaohvisDataParserUseCase.parseHyperEdgeRanges( - nodesColumn, - nodesRow, - filteredData, - axisInfo, - rowInfo, - nodeIdIndex, - this.xAxisNodeGroup, - ); - - return { - rowLabels: nodeListAttr, - hyperEdgeRanges: resultHyperEdgeRanges, - rowDegrees: rowsDegree, - }; - } - - /** - * Sets the x-axis and y-axis node groups from the given PaohvisAxisInfo in the parser. - * @param axisInfo is the new PaohvisAxisInfo that will be used. - */ - private setAxesNodeGroups(axisInfo: PaohvisAxisInfo, entityHorizontal: string, entityVertical: string): void { - this.xAxisNodeGroup = entityHorizontal; - this.yAxisNodeGroup = entityVertical; - } - - /** - * This parses the nodes to get the information that's needed to parse the hyperedges. - * @param nodes are the nodes from the query result. - * @param hyperEdgeDegree is the dictionary where you can find how many edges connected from the node. - * @param nodeOrder is the order in which the nodes should be sorted. - * @param yAxisNodeType is the type of nodes that should be on the y-axis. - * @param xAxisNodeType is the type of nodes that should be on the x-axis. - * @returns the information that's needed to parse the hyperedges. - */ - private static parseNodes(nodes: Node[], yAxisNodeType: string, xAxisNodeType: string): PaohvisNodeInfo { - //const rowNodes = filterRowNodes(nodes, hyperEdgeDegree, yAxisNodeType); - const rowNodes = nodes; // to list all nodes available, even if they do not have connections - - const rowLabels = getIds(rowNodes); - - //make dictionary for finding the index of a row - const yNodesIndexDict: Record<string, number> = {}; - let yNodeIndexCounter = 0; - for (let i = 0; i < rowNodes.length; i++) { - yNodesIndexDict[rowNodes[i]._id] = yNodeIndexCounter; - yNodeIndexCounter++; - } - - const xNodesAttributesDict: Record<string, any> = getXNodesAttributesDict(yAxisNodeType, xAxisNodeType, nodes); - - return { - rowLabels: rowLabels, - xNodesAttributesDict: xNodesAttributesDict, - yNodesIndexDict: yNodesIndexDict, - }; - } - - /** - * Parses the edges to make hyperedge ranges for the Paohvis table. - * @param nodes the unused nodes should already be filtered out. - * @param edges the unused edges should already be filtered out. - * @param axisInfo is the information that's needed to parse the edges to their respective hyperedge range. - * @param rowInfo is the information about the nodes that's needed to parse the edges to their respective hyperedge range. - * @returns the hyperedge ranges that will be used by the Paohvis table. - */ - - private static parseHyperEdgeRanges( - nodesColumn: AugmentedNodeAttributesGraph[], - nodesRow: AugmentedNodeAttributesGraph[], - filteredData: GraphQueryResult, - axisInfo: PaohvisAxisInfo, - rowInfo: PaohvisNodeInfo, - nodeIdIndex: { [id: string]: number }, - xAxisNodeGroup: string, - ): [HyperEdgeRange2[], { [key: string]: number }] { - if (nodesColumn.length == 0 || nodesRow.length == 0) return [[], {}]; - - let resultHyperEdgeRanges: HyperEdgeRange2[] = nodesColumn.map((element) => { - const rangeText: any = element.attributes[axisInfo.selectedAttribute.name]; - const id = element._id; - const degree = 0; - const hyperEdges: HyperEdgeI2 = { - indices: [], - }; - - return { - rangeText, - hyperEdges, - id, - degree, - }; - }); - - const toOrFrom = axisInfo.relation.to == xAxisNodeGroup ? 'to' : 'from'; - const toOrFromOpposite = toOrFrom == 'to' ? 'from' : 'to'; - - // loop through all edges - and creates hyperedge structure (coluumn order/x) - for (let i = 0; i < filteredData.edges.length; i++) { - const edge = filteredData.edges[i]; - const hyperEdgeRange = resultHyperEdgeRanges.find((range) => range._id === edge[toOrFrom])?.hyperEdges; - //console.log(i, edge.from, edge.to, nodeIdIndex[edge[toOrFromOpposite]]); - - hyperEdgeRange?.indices.push(nodeIdIndex[edge[toOrFromOpposite]]); - } - - // Sequential order in indices and count degree ( remove undefined = nodes without connection ) - resultHyperEdgeRanges.forEach((element) => { - element.degree = element.hyperEdges.indices.filter((num) => { - //console.log(num, typeof num); - if (typeof num !== 'number') { - //console.log('Filtered out:', num); - } - return typeof num === 'number'; - }).length; - }); - - resultHyperEdgeRanges.forEach((element) => { - element.hyperEdges.indices.sort((a, b) => a - b); - }); - - // Count nodes degree from structure - const repetitionCounts: { [key: number]: number } = {}; - - for (let indexX = 0; indexX < resultHyperEdgeRanges.length; indexX++) { - countRepetition(resultHyperEdgeRanges[indexX].hyperEdges.indices, repetitionCounts); - } - const repetitionCountLabel: { [key: string]: number } = {}; - // get nodes ids - rowInfo.rowLabels.forEach((arrayElement, index) => { - repetitionCountLabel[arrayElement] = repetitionCounts[index] === undefined ? 0 : repetitionCounts[index]; - }); - - return [resultHyperEdgeRanges, repetitionCountLabel]; - } - - /** Sets new PaohvisFilters. */ - public setPaohvisFilters(filters: PaohvisFilters): void { - this.paohvisFilters = filters; - } -} - -/** Gets a dictionary where you can find the attributes that belong to the nodes on teh x-axis. */ -function getXNodesAttributesDict(yAxisNodeType: string, xAxisNodeType: string, nodes: Node[]) { - const resultXNodesAttributesDict: Record<string, any> = {}; - // it goes to case1: - if (yAxisNodeType == xAxisNodeType) nodes.forEach((node) => (resultXNodesAttributesDict[node!._id] = node.attributes)); - //console.log('cas2 ', yAxisNodeType == xAxisNodeType); - else - nodes.forEach((node) => { - if (getGroupName(node) == xAxisNodeType) resultXNodesAttributesDict[node._id] = node.attributes; - }); - return resultXNodesAttributesDict; -} diff --git a/libs/shared/lib/vis/visualizations/paohvis/utils/dataProcessing.tsx b/libs/shared/lib/vis/visualizations/paohvis/utils/dataProcessing.tsx index c803008e20d84bff6d255807d18e32ea59c75bde..f8cd33af7a5401004f28d7a989913896ebdb7eda 100644 --- a/libs/shared/lib/vis/visualizations/paohvis/utils/dataProcessing.tsx +++ b/libs/shared/lib/vis/visualizations/paohvis/utils/dataProcessing.tsx @@ -1,10 +1,13 @@ -import { cloneDeep } from 'lodash-es'; import { PaohVisProps } from '../paohvis'; -import { PaohvisData, AugmentedNodeAttributesGraph, HyperEdgeRange2, HyperEdgeI2 } from '../types'; +import { PaohvisData, HyperEdgeRange2, HyperEdgeI2 } from '../types'; import AttributeFilterUsecase, { getIds } from './AttributesFilterUseCase'; import { countRepetition } from './utils'; import { Edge, GraphQueryResult, Node } from '@graphpolaris/shared/lib/data-access'; +type AugmentedNode = Node & { + reduced_ids: string[]; +}; + function parseNodes(nodes: Node[], yAxisNodeType: string, xAxisNodeType: string): string[] { //const rowNodes = filterRowNodes(nodes, hyperEdgeDegree, yAxisNodeType); const rowNodes = nodes; @@ -22,8 +25,8 @@ function parseNodes(nodes: Node[], yAxisNodeType: string, xAxisNodeType: string) } function parseHyperEdgeRanges( - nodesColumn: AugmentedNodeAttributesGraph[], - nodesRow: AugmentedNodeAttributesGraph[], + nodesColumn: AugmentedNode[], + nodesRow: AugmentedNode[], filteredData: { nodes: Node[]; edges: Edge[] }, rowInfo: string[], nodeIdIndex: { [id: string]: number }, @@ -44,6 +47,7 @@ function parseHyperEdgeRanges( rangeText, hyperEdges, _id, + reduced_ids: element.reduced_ids, degree, }; }); @@ -56,15 +60,13 @@ function parseHyperEdgeRanges( //console.log(relationTo, columnNode, relationTo == columnNode); - // loop through all edges - and creates hyperedge structure (coluumn order/x) + // loop through all edges - and creates hyperedge structure (column order/x) for (let i = 0; i < filteredData.edges.length; i++) { const edge = filteredData.edges[i]; const pairString = `${edge.from}-${edge.to}`; if (!processedPairs.has(pairString)) { const hyperEdgeRange = resultHyperEdgeRanges.find((range) => range._id === edge[toOrFrom])?.hyperEdges; - //console.log(i, edge.from, edge.to, nodeIdIndex[edge[toOrFromOpposite]], edge[toOrFrom]); - hyperEdgeRange?.indices.push(nodeIdIndex[edge[toOrFromOpposite]]); processedPairs.add(pairString); } @@ -112,9 +114,10 @@ export function parseQueryResult( const skipReduceRow = configuration.attributeRowShow.includes('_id'); const skipReduceColumn = configuration.attributeColumnShow.includes('_id'); const replaceNodeIds: Record<string, string> = {}; + const reducedReplaceNodeIds: Record<string, string[]> = {}; const hashAttributesToIdMap: Record<string, string[]> = {}; - let nodesRow = queryResult.nodes + let nodesRow: AugmentedNode[] = queryResult.nodes .filter((node) => node.label == configuration.rowNode) .map((node) => { const attributes = skipReduceRow @@ -131,10 +134,10 @@ export function parseQueryResult( } else { hashAttributesToIdMap[hash] = [node._id]; } - return { ...node, attributes: attributes }; + return { ...node, attributes: attributes, reduced_ids: [node._id] }; }); - let nodesColumn = queryResult.nodes + let nodesColumn: AugmentedNode[] = queryResult.nodes .filter((node) => node.label == configuration.columnNode) .map((node) => { const attributes = skipReduceColumn @@ -150,20 +153,28 @@ export function parseQueryResult( } else { hashAttributesToIdMap[hash] = [node._id]; } - return { ...node, attributes: attributes }; + return { ...node, attributes: attributes, reduced_ids: [node._id] }; }); // Make id->id replace map Object.entries(hashAttributesToIdMap).forEach(([k, v]) => { if (v.length <= 1) return; + v.slice(1).forEach((id) => { replaceNodeIds[id] = v[0]; + if (!(id in reducedReplaceNodeIds)) reducedReplaceNodeIds[id] = [v[0]]; + else reducedReplaceNodeIds[id].push(v[0]); }); }); + const toRemoveIds = new Set(Object.keys(replaceNodeIds)); if (mergeData) { - nodesRow = nodesRow.filter((node) => !toRemoveIds.has(node._id)); - nodesColumn = nodesColumn.filter((node) => !toRemoveIds.has(node._id)); + nodesRow = nodesRow + .filter((node) => !toRemoveIds.has(node._id)) + .map((node) => ({ ...node, reduced_ids: [node._id, ...(reducedReplaceNodeIds?.[node._id] || [])] })); + nodesColumn = nodesColumn + .filter((node) => !toRemoveIds.has(node._id)) + .map((node) => ({ ...node, reduced_ids: [node._id, ...(reducedReplaceNodeIds?.[node._id] || [])] })); } const augmentedNodes = [...nodesRow, ...nodesColumn]; diff --git a/libs/shared/lib/vis/visualizations/paohvis/utils/utils.tsx b/libs/shared/lib/vis/visualizations/paohvis/utils/utils.tsx index 0f1f1e1ced21fecc540f1212a35c1bc872bf96ca..2726a5e5b1cbb8fb50ab9b7ea074cfbd74ae1e14 100644 --- a/libs/shared/lib/vis/visualizations/paohvis/utils/utils.tsx +++ b/libs/shared/lib/vis/visualizations/paohvis/utils/utils.tsx @@ -6,12 +6,12 @@ import { connectionFromTo, idConnections, GraphData, - AugmentedNodeAttributesGraph, RowInformation, Data2RenderI, } from '../types'; import { MultiGraph } from 'graphology'; -import * as d3 from 'd3'; +import { Node } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice'; +import { group } from 'd3'; /** * Takes a schema result and calculates all the entity names, and relation names and attribute names per entity. @@ -167,7 +167,7 @@ export function findConnectionsNodes( originIDs: string[], graphStructure: MultiGraph, labelNode: string, - edges: AugmentedNodeAttributesGraph[], + edges: Node[], ): connectionFromTo[] { const neighborMap: idConnections = {}; const neighborMapNo: idConnections = {}; @@ -336,7 +336,7 @@ export function wrapperForEdge(data: idConnections, attributes: any): connection export const buildGraphology = (data: GraphData): MultiGraph => { const graph = new MultiGraph(); - const nodeMap = new Map<string, AugmentedNodeAttributesGraph>(); + const nodeMap = new Map<string, Node>(); data.nodes.forEach((node) => { nodeMap.set(node._id, node); }); @@ -632,7 +632,7 @@ export const processDataColumn = (dataColumn: string, firstRowData: any, data: a newData2Render.typeData === 'datetime' || newData2Render.typeData === 'time' ) { - const groupedData = d3.group(data, (d) => d.attribute[dataColumn]); + const groupedData = group(data, (d) => d.attribute[dataColumn]); categoryCounts = Array.from(groupedData, ([category, items]) => ({ category: category as string, count: items.length, @@ -641,7 +641,7 @@ export const processDataColumn = (dataColumn: string, firstRowData: any, data: a newData2Render.numUniqueElements = categoryCounts.length; newData2Render.data = categoryCounts; } else if (newData2Render.typeData === 'bool') { - const groupedData = d3.group(data, (d) => d.attribute[dataColumn]); + const groupedData = group(data, (d) => d.attribute[dataColumn]); categoryCounts = Array.from(groupedData, ([category, items]) => ({ category: category as string, @@ -660,7 +660,7 @@ export const processDataColumn = (dataColumn: string, firstRowData: any, data: a newData2Render.data = categoryCounts; } else { // there is also array type, when considering labels - const groupedData = d3.group(data, (d) => (d.attribute[dataColumn] as any)?.[0]); + const groupedData = group(data, (d) => (d.attribute[dataColumn] as any)?.[0]); categoryCounts = Array.from(groupedData, ([category, items]) => ({ category: category as string,