From c19a23bcdfa43e6c1e5d0d6e62fe532a45934499 Mon Sep 17 00:00:00 2001
From: Marcos Pieras <pieras.marcos@gmail.com>
Date: Mon, 13 May 2024 12:45:18 +0000
Subject: [PATCH] fix(vis): flickering on hyperedge of paohvis

---
 .../paohvis/components/CustomLine.tsx         |   4 +-
 .../paohvis/components/HyperRangeBlock.tsx    |  16 +-
 .../paohvis/components/RowLabels.tsx          |   6 +-
 .../vis/visualizations/paohvis/paohvis.tsx    | 224 +++++++++++-------
 .../lib/vis/visualizations/paohvis/types.ts   |   6 +
 .../vis/visualizations/tablevis/tablevis.tsx  |  87 ++++---
 6 files changed, 212 insertions(+), 131 deletions(-)

diff --git a/libs/shared/lib/vis/visualizations/paohvis/components/CustomLine.tsx b/libs/shared/lib/vis/visualizations/paohvis/components/CustomLine.tsx
index df2a4a5e0..d6cd9e5c7 100644
--- a/libs/shared/lib/vis/visualizations/paohvis/components/CustomLine.tsx
+++ b/libs/shared/lib/vis/visualizations/paohvis/components/CustomLine.tsx
@@ -9,8 +9,6 @@ interface LineProps {
   fill: string;
 }
 
-const CustomLine: React.FC<LineProps> = (props) => {
+export const CustomLine: React.FC<LineProps> = (props) => {
   return <line x1={props.x1} x2={props.x2} y1={props.y1} y2={props.y2} strokeWidth={props.strokeWidth} fill={props.fill} />;
 };
-
-export default CustomLine;
diff --git a/libs/shared/lib/vis/visualizations/paohvis/components/HyperRangeBlock.tsx b/libs/shared/lib/vis/visualizations/paohvis/components/HyperRangeBlock.tsx
index 0b23a88c0..a313a2950 100644
--- a/libs/shared/lib/vis/visualizations/paohvis/components/HyperRangeBlock.tsx
+++ b/libs/shared/lib/vis/visualizations/paohvis/components/HyperRangeBlock.tsx
@@ -1,12 +1,12 @@
 import React, { useEffect, useState, useMemo } from 'react';
-import CustomLine from './CustomLine';
-import { intersectionElements } from '../utils/utils';
-import { PaohvisDataPaginated, RowInformation } from '../types';
+import { CustomLine } from './CustomLine';
+import { PaohvisDataPaginated, RowInformation, LinesHyperedges } from '../types';
 import { ArrowDownward, ArrowUpward, Sort } from '@mui/icons-material';
 import { select } from 'd3';
 
 interface HyperEdgeRangesBlockProps {
   dataModel: PaohvisDataPaginated;
+  dataLinesHyperedges: LinesHyperedges[];
   rowHeight: number;
   yOffset: number;
   rowLabelColumnWidth: number;
@@ -29,6 +29,7 @@ interface HyperEdgeRangesBlockProps {
 
 export const HyperEdgeRangesBlock: React.FC<HyperEdgeRangesBlockProps> = ({
   dataModel,
+  dataLinesHyperedges,
   rowHeight,
   yOffset,
   rowLabelColumnWidth,
@@ -45,6 +46,7 @@ export const HyperEdgeRangesBlock: React.FC<HyperEdgeRangesBlockProps> = ({
   onMouseLeaveHyperEdge,
   handleClickHeaderSorting,
 }) => {
+  /*
   const [linePositions, setLinePositions] = useState<{ y0: number; y1: number; valid: boolean }[]>([]);
 
   useEffect(() => {
@@ -53,11 +55,13 @@ export const HyperEdgeRangesBlock: React.FC<HyperEdgeRangesBlockProps> = ({
         //console.log(hyperEdgeRange);
         return intersectionElements([currentPageRows.startIndexRow, currentPageRows.endIndexRow], hyperEdgeRange.hyperEdges.indices);
       });
+      console.log('internal: ', newLinePositions);
       setLinePositions(newLinePositions);
     } else {
       setLinePositions([]);
     }
   }, [currentPageRows, dataModel]);
+  */
 
   const accumulatedWidthHeaders = useMemo(() => {
     const accumulatedWidths: number[] = [0];
@@ -281,13 +285,13 @@ export const HyperEdgeRangesBlock: React.FC<HyperEdgeRangesBlockProps> = ({
                 key={'groupBlockExtra-' + indexHyperEdgeRange}
                 transform={`translate(${rowLabelColumnWidth + indexHyperEdgeRange * rowHeight + 0.5 * rowHeight},${yOffset + 0.5 * rowHeight})`}
               >
-                {currentPageRows && linePositions[indexHyperEdgeRange]?.valid && (
+                {currentPageRows && dataLinesHyperedges[indexHyperEdgeRange]?.valid && (
                   <line
                     key={'hyperRangeBlockLine line_' + indexHyperEdgeRange}
                     x1={0}
-                    y1={(linePositions[indexHyperEdgeRange].y0 - currentPageRows.startIndexRow) * rowHeight}
+                    y1={(dataLinesHyperedges[indexHyperEdgeRange].y0 - currentPageRows.startIndexRow) * rowHeight}
                     x2={0}
-                    y2={(linePositions[indexHyperEdgeRange].y1 - currentPageRows.startIndexRow) * rowHeight}
+                    y2={(dataLinesHyperedges[indexHyperEdgeRange].y1 - currentPageRows.startIndexRow) * rowHeight}
                     strokeWidth={1.5}
                     stroke="hsl(var(--clr-sec--800))"
                   />
diff --git a/libs/shared/lib/vis/visualizations/paohvis/components/RowLabels.tsx b/libs/shared/lib/vis/visualizations/paohvis/components/RowLabels.tsx
index 1fae1e321..425472216 100644
--- a/libs/shared/lib/vis/visualizations/paohvis/components/RowLabels.tsx
+++ b/libs/shared/lib/vis/visualizations/paohvis/components/RowLabels.tsx
@@ -1,5 +1,5 @@
 import React, { useEffect, useState } from 'react';
-import CustomLine from './CustomLine';
+import { CustomLine } from './CustomLine';
 import { RowInformation } from '../types';
 import { ArrowDownward, ArrowUpward, Sort } from '@mui/icons-material';
 import { select, selectAll } from 'd3';
@@ -18,7 +18,7 @@ interface RowLabelsProps {
   handleClickHeaderSorting: (event: React.MouseEvent<SVGGElement, MouseEvent>) => void;
 }
 
-export const RowLabels: React.FC<RowLabelsProps> = ({
+export const RowLabels = ({
   dataRows,
   rowHeight,
   yOffset,
@@ -30,7 +30,7 @@ export const RowLabels: React.FC<RowLabelsProps> = ({
   onMouseEnterRowLabels,
   onMouseLeaveRowLabels,
   handleClickHeaderSorting,
-}) => {
+}: RowLabelsProps) => {
   const accumulatedWidthHeaders = [0];
   let sum = 0;
   dataRows.forEach((row, index) => {
diff --git a/libs/shared/lib/vis/visualizations/paohvis/paohvis.tsx b/libs/shared/lib/vis/visualizations/paohvis/paohvis.tsx
index 0e321f471..48b70b406 100644
--- a/libs/shared/lib/vis/visualizations/paohvis/paohvis.tsx
+++ b/libs/shared/lib/vis/visualizations/paohvis/paohvis.tsx
@@ -1,12 +1,12 @@
 import React, { useEffect, useRef, useState, useMemo } from 'react';
-import { PaohvisDataPaginated, RowInformation } from './types';
+import { PaohvisDataPaginated, RowInformation, LinesHyperedges } from './types';
 import { parseQueryResult } from './utils/dataProcessing';
 import { GraphMetadata } from '@graphpolaris/shared/lib/data-access/statistics';
 
 import { RowLabels } from './components/RowLabels';
 import { HyperEdgeRangesBlock } from './components/HyperRangeBlock';
 
-import { sortRowInformation, sortIndices } from './utils/utils';
+import { sortRowInformation, sortIndices, intersectionElements } from './utils/utils';
 import { select, selectAll } from 'd3';
 
 import { SettingsContainer } from '@graphpolaris/shared/lib/vis/components/config';
@@ -17,6 +17,7 @@ import { EntityPill } from '@graphpolaris/shared/lib/components/pills/Pill';
 import { ArrowDropDown } from '@mui/icons-material';
 import { cloneDeep } from 'lodash-es';
 import { useImmer } from 'use-immer';
+import PillDropdown, { PillDropdownProps } from '@graphpolaris/shared/lib/components/PillDropdown';
 
 export type PaohVisProps = {
   rowHeight: number;
@@ -75,6 +76,9 @@ export const PaohVis = ({ data, graphMetadata, schema, configuration: conf, upda
   const [permutationIndicesColumn, setPermutationIndicesColumn] = useState<number[]>([]);
   const [previousHeaderColumn, setPreviousHeaderColumn] = useState<string>('none');
 
+  // lines hyperEdge
+  const [lineHyperEdges, setLineHyperEdges] = useState<LinesHyperedges[]>([]);
+
   //
   const [indicesRowsForColumnSort, setIndicesRowsForColumnSort] = useState<number[]>([]);
   const [indicesColumnForRowSort, setIndicesColumnForRowSort] = useState<number[]>([]);
@@ -349,6 +353,14 @@ export const PaohVis = ({ data, graphMetadata, schema, configuration: conf, upda
       draft.data.hyperEdgeRanges = dataModelOriginalTemporalSorted;
     });
 
+    // Update lines
+    if (currentPageRows) {
+      const newLinePositions = sortedArrayDataModelFiltered.map((hyperEdgeRange) => {
+        return intersectionElements([currentPageRows?.startIndexRow, currentPageRows?.endIndexRow], hyperEdgeRange.hyperEdges.indices);
+      });
+      setLineHyperEdges(newLinePositions);
+    }
+
     /*
     setDataModel({
       rowLabels: dataModelOriginalTemporal.rowLabels.slice(currentPageRows?.startIndexRow, currentPageRows?.endIndexRow),
@@ -462,6 +474,14 @@ export const PaohVis = ({ data, graphMetadata, schema, configuration: conf, upda
       draft.pageData.hyperEdgeRanges = sortedArrayDataModelFiltered;
       draft.data.hyperEdgeRanges = sortedArrayDataModel;
     });
+
+    // Update lines hyperedges
+    if (currentPageRows?.startIndexRow !== undefined && currentPageRows?.endIndexRow !== undefined) {
+      const newLinePositions = sortedArrayDataModelFiltered.map((hyperEdgeRange) => {
+        return intersectionElements([currentPageRows?.startIndexRow, currentPageRows?.endIndexRow], hyperEdgeRange.hyperEdges.indices);
+      });
+      setLineHyperEdges(newLinePositions);
+    }
   };
 
   useEffect(() => {
@@ -530,6 +550,15 @@ export const PaohVis = ({ data, graphMetadata, schema, configuration: conf, upda
         originalData: newData,
       });
 
+      // Update lines hyperedges
+      const newLinePositions = newData.hyperEdgeRanges.slice(0, configPaohvis.columnsMaxPerPage).map((hyperEdgeRange) => {
+        return intersectionElements(
+          [0, Math.min(configPaohvis.rowsMaxPerPage, newData.rowLabels.length)],
+          hyperEdgeRange.hyperEdges.indices,
+        );
+      });
+      setLineHyperEdges(newLinePositions);
+
       const originalIndicesColumns = Array.from({ length: newData.hyperEdgeRanges.length + 1 }, (_, index) => index);
 
       setIndicesColumnForRowSort(originalIndicesColumns);
@@ -745,6 +774,14 @@ export const PaohVis = ({ data, graphMetadata, schema, configuration: conf, upda
       draft.pageData.hyperEdgeRanges = slicedHyperEdgeRanges;
     });
 
+    // Update lines hyperedges
+    if (currentPageRows?.startIndexRow !== undefined && currentPageRows?.endIndexRow !== undefined) {
+      const newLinePositions = slicedHyperEdgeRanges.map((hyperEdgeRange) => {
+        return intersectionElements([currentPageRows?.startIndexRow, currentPageRows?.endIndexRow], hyperEdgeRange.hyperEdges.indices);
+      });
+      setLineHyperEdges(newLinePositions);
+    }
+
     // columns
 
     const dataColumnsSorted = cloneDeep(informationColumnAllData);
@@ -790,6 +827,12 @@ export const PaohVis = ({ data, graphMetadata, schema, configuration: conf, upda
       startIndexRow: startIndexRow,
       endIndexRow: endIndexRow,
     });
+
+    // Update lines hyperedges
+    const newLinePositions = dataModel.pageData.hyperEdgeRanges.map((hyperEdgeRange) => {
+      return intersectionElements([startIndexRow, endIndexRow], hyperEdgeRange.hyperEdges.indices);
+    });
+    setLineHyperEdges(newLinePositions);
   };
 
   return (
@@ -818,6 +861,7 @@ export const PaohVis = ({ data, graphMetadata, schema, configuration: conf, upda
 
       <HyperEdgeRangesBlock
         dataModel={dataModel}
+        dataLinesHyperedges={lineHyperEdges}
         rowHeight={configuration.rowHeight}
         yOffset={computedSizesSvg.colWidth}
         rowLabelColumnWidth={widthTotalRowInformation}
@@ -892,97 +936,105 @@ const PaohSettings = ({
 
   return (
     <SettingsContainer>
-      <span className="text-xs font-semibold">Node used in Row</span>
-      <Input
-        type="dropdown"
-        value={configuration.rowNode}
-        options={graphMetadata.nodes.labels}
-        onChange={(val) => updateSettings({ rowNode: val })}
-        overrideRender={<EntityPill title={configuration.rowNode} />}
-      />
-      <Button
-        className="w-full text-justify justify-start"
-        type="secondary"
-        variant="ghost"
-        size="sm"
-        onClick={toggleCollapseAttrRows}
-        iconComponent={<ArrowDropDown />}
-      >
-        attributes:{' '}
-      </Button>
-      {!areCollapsedAttrRows && (
-        <div className="">
+      <div className="overflow-x-hidden">
+        <div>
+          <span className="text-xs font-semibold">Node used in Row</span>
           <Input
-            type="checkbox"
-            value={configuration.attributeRowShow}
-            options={rowNodeAttributes}
-            onChange={(val: string[] | string) => {
-              const updatedVal = Array.isArray(val) ? val : [val];
-              updateSettings({ attributeRowShow: updatedVal });
-            }}
+            type="dropdown"
+            value={configuration.rowNode}
+            options={graphMetadata.nodes.labels}
+            onChange={(val) => updateSettings({ rowNode: val })}
+            overrideRender={<EntityPill title={configuration.rowNode} />}
           />
         </div>
-      )}
-      <span className="text-xs font-semibold">Node used in Column</span>
-      <Input
-        type="dropdown"
-        value={configuration.columnNode}
-        options={graphMetadata.nodes.labels}
-        onChange={(val) => updateSettings({ columnNode: val })}
-        overrideRender={<EntityPill title={configuration.columnNode} />}
-      />
-      <Button
-        className="w-full text-justify justify-start"
-        type="secondary"
-        variant="ghost"
-        size="sm"
-        onClick={toggleCollapseAttrColumns}
-        iconComponent={<ArrowDropDown />}
-      >
-        attributes:{' '}
-      </Button>
-
-      {!areCollapsedAttrColumns && (
-        <div className="">
+        <Button
+          className="w-full text-justify justify-start"
+          type="secondary"
+          variant="ghost"
+          size="sm"
+          onClick={toggleCollapseAttrRows}
+          iconComponent={<ArrowDropDown />}
+        >
+          attributes:{' '}
+        </Button>
+        {!areCollapsedAttrRows && (
+          <div className="">
+            <Input
+              type="checkbox"
+              value={configuration.attributeRowShow}
+              options={rowNodeAttributes}
+              onChange={(val: string[] | string) => {
+                const updatedVal = Array.isArray(val) ? val : [val];
+                updateSettings({ attributeRowShow: updatedVal });
+              }}
+            />
+          </div>
+        )}
+
+        <div>
+          <span className="text-xs font-semibold">Node used in Column</span>
           <Input
-            type="checkbox"
-            value={configuration.attributeColumnShow}
-            options={columnsNodeAttributes}
-            onChange={(val: string[] | string) => {
-              const updatedVal = Array.isArray(val) ? val : [val];
-              updateSettings({ attributeColumnShow: updatedVal });
-            }}
+            type="dropdown"
+            value={configuration.columnNode}
+            options={graphMetadata.nodes.labels}
+            onChange={(val) => updateSettings({ columnNode: val })}
+            overrideRender={<EntityPill title={configuration.columnNode} />}
           />
         </div>
-      )}
 
-      <Input type="number" label="Row height" value={configuration.rowHeight} onChange={(val) => updateSettings({ rowHeight: val })} />
-
-      <Input
-        type="number"
-        label="# Rows"
-        value={configuration.numRowsDisplay}
-        onChange={(val) => updateSettings({ numRowsDisplay: val })}
-      />
-      <Input
-        type="number"
-        label="# Columns"
-        value={configuration.numColumnsDisplay}
-        onChange={(val) => updateSettings({ numColumnsDisplay: val })}
-      />
-      <Input
-        type="number"
-        label="Row jump sensitivity"
-        value={configuration.rowJumpAmount}
-        onChange={(val) => updateSettings({ rowJumpAmount: val })}
-      />
-      <Input
-        type="number"
-        label="Column jump sensitivity"
-        value={configuration.colJumpAmount}
-        onChange={(val) => updateSettings({ colJumpAmount: val })}
-      />
-      <Input type="boolean" label="Merge Data" value={configuration.mergeData} onChange={(val) => updateSettings({ mergeData: val })} />
+        <Button
+          className="w-full text-justify justify-start"
+          type="secondary"
+          variant="ghost"
+          size="sm"
+          onClick={toggleCollapseAttrColumns}
+          iconComponent={<ArrowDropDown />}
+        >
+          attributes:{' '}
+        </Button>
+
+        {!areCollapsedAttrColumns && (
+          <div className="">
+            <Input
+              type="checkbox"
+              value={configuration.attributeColumnShow}
+              options={columnsNodeAttributes}
+              onChange={(val: string[] | string) => {
+                const updatedVal = Array.isArray(val) ? val : [val];
+                updateSettings({ attributeColumnShow: updatedVal });
+              }}
+            />
+          </div>
+        )}
+
+        <Input type="number" label="Row height" value={configuration.rowHeight} onChange={(val) => updateSettings({ rowHeight: val })} />
+
+        <Input
+          type="number"
+          label="# Rows"
+          value={configuration.numRowsDisplay}
+          onChange={(val) => updateSettings({ numRowsDisplay: val })}
+        />
+        <Input
+          type="number"
+          label="# Columns"
+          value={configuration.numColumnsDisplay}
+          onChange={(val) => updateSettings({ numColumnsDisplay: val })}
+        />
+        <Input
+          type="number"
+          label="Row jump sensitivity"
+          value={configuration.rowJumpAmount}
+          onChange={(val) => updateSettings({ rowJumpAmount: val })}
+        />
+        <Input
+          type="number"
+          label="Column jump sensitivity"
+          value={configuration.colJumpAmount}
+          onChange={(val) => updateSettings({ colJumpAmount: val })}
+        />
+        <Input type="boolean" label="Merge Data" value={configuration.mergeData} onChange={(val) => updateSettings({ mergeData: val })} />
+      </div>
     </SettingsContainer>
   );
 };
diff --git a/libs/shared/lib/vis/visualizations/paohvis/types.ts b/libs/shared/lib/vis/visualizations/paohvis/types.ts
index 0d28f205c..705120df6 100644
--- a/libs/shared/lib/vis/visualizations/paohvis/types.ts
+++ b/libs/shared/lib/vis/visualizations/paohvis/types.ts
@@ -28,6 +28,12 @@ export type RowInformation = {
   data: any[];
 }[];
 
+export type LinesHyperedges = {
+  y0: number;
+  y1: number;
+  valid: boolean;
+};
+
 /** The ranges of the hyperEdges consisting of the name of the range with all adequate hyperEdges. */
 /*
 export type HyperEdgeRange = {
diff --git a/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx b/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx
index 3aa5192f3..7a8137151 100644
--- a/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx
+++ b/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx
@@ -1,10 +1,13 @@
-import React, { useEffect, useMemo, useRef } from 'react';
+import React, { useEffect, useMemo, useRef, useState } from 'react';
 import { Table, AugmentedNodeAttributes } from './components/Table';
 import { SchemaAttribute } from '../../../schema';
 import { VisualizationPropTypes, VISComponentType } from '../../common';
 import { Input } from '@graphpolaris/shared/lib/components/inputs';
 import { GraphMetadata } from '@graphpolaris/shared/lib/data-access/statistics';
 import { SettingsContainer } from '@graphpolaris/shared/lib/vis/components/config';
+import PillDropdown, { PillDropdownProps } from '@graphpolaris/shared/lib/components/PillDropdown';
+import { Button } from '@graphpolaris/shared/lib/components/buttons';
+import { ArrowDropDown } from '@mui/icons-material';
 
 export type TableProps = {
   showBarplot: boolean;
@@ -77,6 +80,10 @@ const TableSettings = ({
     }
   }, [graphMetadata]);
 
+  const [areCollapsedAttr, setAreCollapsedAttr] = useState<boolean>(true);
+  const toggleCollapseAttr = () => {
+    setAreCollapsedAttr(!areCollapsedAttr);
+  };
   const selectedNodeAttributes = useMemo(() => {
     if (configuration.displayEntity) {
       const nodeType = graphMetadata.nodes.types[configuration.displayEntity];
@@ -95,38 +102,52 @@ const TableSettings = ({
 
   return (
     <SettingsContainer>
-      <Input
-        type="dropdown"
-        label="Select entity"
-        value={configuration.displayEntity}
-        onChange={(val) => updateSettings({ displayEntity: val })}
-        options={graphMetadata.nodes.labels}
-      />
-      <Input
-        type="boolean"
-        label="Show barplot"
-        value={configuration.showBarplot}
-        onChange={(val) => updateSettings({ showBarplot: val })}
-      />
-      <Input
-        type="dropdown"
-        label="Items per page"
-        value={configuration.itemsPerPage}
-        onChange={(val) => updateSettings({ itemsPerPage: val })}
-        options={[10, 25, 50, 100]}
-      />
-      <div>
-        <span className="text-sm">Attributes to display</span>
-        <div className="">
-          <Input
-            type="checkbox"
-            value={configuration.displayAttributes}
-            options={selectedNodeAttributes}
-            onChange={(val: string[] | string) => {
-              const updatedVal = Array.isArray(val) ? val : [val];
-              updateSettings({ displayAttributes: updatedVal });
-            }}
-          />
+      <div className="overflow-x-hidden w-6">
+        <Input
+          type="dropdown"
+          label="Select entity"
+          value={configuration.displayEntity}
+          onChange={(val) => updateSettings({ displayEntity: val })}
+          options={graphMetadata.nodes.labels}
+        />
+        <Input
+          type="boolean"
+          label="Show barplot"
+          value={configuration.showBarplot}
+          onChange={(val) => updateSettings({ showBarplot: val })}
+        />
+        <Input
+          type="dropdown"
+          label="Items per page"
+          value={configuration.itemsPerPage}
+          onChange={(val) => updateSettings({ itemsPerPage: val })}
+          options={[10, 25, 50, 100]}
+        />
+        <div>
+          <span className="text-sm">Attributes to display:</span>
+          <Button
+            className="w-full text-justify justify-start"
+            type="secondary"
+            variant="ghost"
+            size="sm"
+            onClick={toggleCollapseAttr}
+            iconComponent={<ArrowDropDown />}
+          >
+            attributes:{' '}
+          </Button>
+          <div className="">
+            {!areCollapsedAttr && (
+              <Input
+                type="checkbox"
+                value={configuration.displayAttributes}
+                options={selectedNodeAttributes}
+                onChange={(val: string[] | string) => {
+                  const updatedVal = Array.isArray(val) ? val : [val];
+                  updateSettings({ displayAttributes: updatedVal });
+                }}
+              />
+            )}
+          </div>
         </div>
       </div>
     </SettingsContainer>
-- 
GitLab