import React, { useEffect, useState, useMemo } from 'react'; import { CustomLine } from './CustomLine'; import { PaohvisDataPaginated, RowInformation, LinesHyperEdges } from '../types'; import { ArrowDownward, ArrowUpward, ContactPageSharp, Sort } from '@mui/icons-material'; import { select } from 'd3'; import { visualizationColors, tailwindColors } from 'config'; interface HyperEdgeRangesBlockProps { dataModel: PaohvisDataPaginated; dataLinesHyperedges: LinesHyperEdges[]; rowHeight: number; yOffset: number; rowLabelColumnWidth: number; classTopTextColumns: string; currentPageRows?: { startIndexRow: number; endIndexRow: number; } | null; onMouseEnterHyperEdge: (event: React.MouseEvent<SVGGElement, MouseEvent>) => void; onMouseLeaveHyperEdge: () => void; rowsMaxPerPage: number; marginText: number; sortState: string; numRows: number; columnHeaderInformation: RowInformation; widthColumns: number; headerState: string; configStyle: { [key: string]: string }; handleClickHeaderSorting: (event: React.MouseEvent<SVGGElement, MouseEvent>) => void; } export const HyperEdgeRangesBlock: React.FC<HyperEdgeRangesBlockProps> = ({ dataModel, dataLinesHyperedges, rowHeight, yOffset, rowLabelColumnWidth, classTopTextColumns, currentPageRows, rowsMaxPerPage, marginText, widthColumns, configStyle, numRows, sortState, headerState, columnHeaderInformation, onMouseEnterHyperEdge, onMouseLeaveHyperEdge, handleClickHeaderSorting, }) => { const accumulatedWidthHeaders = useMemo(() => { const accumulatedWidths: number[] = [0]; let sum = 0; columnHeaderInformation.forEach((column, index) => { sum += column.width; if (index !== columnHeaderInformation.length - 1) { accumulatedWidths.push(sum); } }); return accumulatedWidths; }, [columnHeaderInformation]); const adjustedFontSize = useMemo(() => { // text-sm: 0.75 rem, rowHeight: 20 return Math.max(0.4, (rowHeight * 0.75) / 20); }, [rowHeight]); const [isHovered, setIsHovered] = useState(false); const [hoverRowIndex, setHoverRowIndex] = useState<number | null>(null); const [iconComponents, setIconComponents] = useState<{ [key: number]: JSX.Element }[]>(Array(columnHeaderInformation.length).fill({})); const [iconColors, setIconColors] = useState<string[]>([]); useEffect(() => { const iconColorsTemporal: string[] = []; const updatedIconComponents = columnHeaderInformation.map((row, indexRows) => { let iconComponent: JSX.Element = <></>; if (row.header == headerState) { switch (sortState) { case 'asc': iconComponent = <ArrowUpward />; break; case 'desc': iconComponent = <ArrowDownward />; break; case 'original': iconComponent = <Sort />; break; default: iconComponent = <></>; } iconColorsTemporal.push('hsl(var(--clr-sec--800))'); } else { iconComponent = <></>; iconColorsTemporal.push('hsl(var(--clr-sec--500))'); } if (indexRows == hoverRowIndex && headerState != row.header) { iconComponent = <Sort />; } return { [indexRows]: iconComponent }; }); setIconColors(iconColorsTemporal); setIconComponents(updatedIconComponents); }, [sortState, headerState, hoverRowIndex]); return ( <> <g key={'hyperEdgeInformationTop'} className="hyperEdgeInformation"> <g key={'hyperEdgeLinesRows'} transform={`translate(${rowLabelColumnWidth},${yOffset})`}> {[...Array(numRows)].map((_, i) => ( <React.Fragment key={`horizontalLineRows-${i}`}> {i === 0 && ( <CustomLine key={`horizontalLineRowsTop-${i}`} className={`horizontalLineRowsTop-${i}`} x1={0} x2={dataModel.pageData.hyperEdgeRanges.length * rowHeight} y1={-yOffset} y2={-yOffset} strokeWidth={0.025 * rowHeight} fill={configStyle.colorLinesGrid} /> )} {i === numRows - 1 && ( <CustomLine key={`horizontalLineRowsExtra-${i}`} x1={0} x2={dataModel.pageData.hyperEdgeRanges.length * rowHeight} y1={rowHeight * (i + 1)} y2={rowHeight * (i + 1)} strokeWidth={0.025 * rowHeight} fill={configStyle.colorLinesGrid} /> )} <CustomLine key={`horizontalLineRowsBottom-${i}`} x1={0} x2={dataModel.pageData.hyperEdgeRanges.length * rowHeight} y1={rowHeight * i} y2={rowHeight * i} strokeWidth={0.025 * rowHeight} fill={configStyle.colorLinesGrid} /> </React.Fragment> ))} </g> <g key={'hyperEdgeInformation'} className="hyperEdgeInformation text-columns " transform={'translate(' + rowLabelColumnWidth + ',' + widthColumns + ')'} > {columnHeaderInformation[0] && columnHeaderInformation[0].data.map((rowLabel, indexRows) => ( <g key={'colsLabel col-' + indexRows} className={'colsLabel col-' + indexRows} transform={'translate(' + indexRows * rowHeight + ',0)rotate(-90,0,0)'} > {columnHeaderInformation.map((row, index) => ( <g key={'text-col-' + index} transform={'translate(' + (accumulatedWidthHeaders[index] + rowHeight) + ',0)'}> {row.data[indexRows] !== undefined && row.data[indexRows] !== '' && (typeof row.data[indexRows] !== 'object' || Array.isArray(row.data[indexRows])) ? ( <> <foreignObject x="0" y="0" width={0 === index ? row.width + rowHeight : row.width} height={rowHeight}> <div className="w-full h-full flex justify-center items-start"> <span className={`${classTopTextColumns}`} style={{ fontSize: `${adjustedFontSize}rem`, }} > {row.data[indexRows].toString()} </span> </div> </foreignObject> </> ) : ( <rect width={0 === index ? row.width + rowHeight : row.width} height={rowHeight} fill={'url(#diagonal-lines)'} strokeWidth={0} ></rect> )} </g> ))} </g> ))} </g> <g key={'columnInformationHeaders'} className={'columnInformationHeaders'} transform={'translate(' + (rowLabelColumnWidth - rowHeight) + ',' + widthColumns + ')'} > {columnHeaderInformation.map((headerData, headerIndex) => ( <g key={'gHeadersColumns-' + headerIndex} className={`headersCols-${headerData.header} cursor-pointer`} transform={'translate(' + 0 + ',' + (-accumulatedWidthHeaders[headerIndex] - rowHeight) + ')rotate(-90, 0,0)'} onClick={(event) => { handleClickHeaderSorting(event); }} onMouseEnter={(event) => { setHoverRowIndex(headerIndex); setIsHovered(true); select(event.currentTarget).select('rect').attr('fill', 'hsl(var(--clr-sec--300))'); }} onMouseLeave={(event) => { setIsHovered(sortState !== 'original'); setHoverRowIndex(null); select(event.currentTarget).select('rect').attr('fill', 'hsl(var(--clr-sec--200))'); }} > <rect width={0 === headerIndex ? headerData.width + rowHeight : headerData.width} height={rowHeight} fill={'hsl(var(--clr-sec--200))'} opacity={1.0} strokeWidth={0} ></rect> <foreignObject x="0" y="0" width={headerData.width} height={rowHeight}> <div className="w-full h-full flex justify-left"> <span className={`${classTopTextColumns}`} style={{ fontSize: `${adjustedFontSize}rem` }}> {headerData.header !== undefined && (typeof headerData.header !== 'object' || Array.isArray(headerData.header)) ? (headerData.header as any).toString() : ' '} </span> </div> </foreignObject> {iconComponents[headerIndex] && isHovered && ( <svg xmlns="http://www.w3.org/2000/svg" width={2 * headerData.width - rowHeight} height={rowHeight} style={{ color: iconColors[headerIndex], stroke: 'none' }} > {iconComponents[headerIndex][headerIndex]} </svg> )} <CustomLine x1={headerData.width} x2={headerData.width} y1={0} y2={rowHeight * (dataModel.pageData.hyperEdgeRanges.length + 1)} strokeWidth={0.025 * rowHeight} fill={configStyle.colorLinesGrid} /> </g> ))} </g> {dataModel.pageData.hyperEdgeRanges.map((hyperEdgeRange, indexHyperEdgeRange) => ( <React.Fragment key={indexHyperEdgeRange}> <g className={'hyperEdgeBlockLinesRef hyperEdgeLines-col-' + indexHyperEdgeRange} transform={'translate(' + (rowLabelColumnWidth + indexHyperEdgeRange * rowHeight) + ',' + widthColumns + ')rotate(-90,0,0)'} > <CustomLine x1={-rowHeight * numRows} x2={indexHyperEdgeRange === 0 ? 0 : yOffset} y1={0} y2={0} strokeWidth={0.025 * rowHeight} fill={configStyle.colorLinesGrid} /> </g> {indexHyperEdgeRange === dataModel.pageData.hyperEdgeRanges.length - 1 && ( <g key={'hyperEdgeBlockLinesRef-' + indexHyperEdgeRange} className={'hyperEdgeBlockLinesRef hyperEdgeLines-col-' + indexHyperEdgeRange} transform={ 'translate(' + (rowLabelColumnWidth + (dataModel.pageData.hyperEdgeRanges.length - 1) * rowHeight) + ',' + widthColumns + ')rotate(-90,0,0)' } > <CustomLine x1={-rowHeight * numRows} x2={yOffset} y1={rowHeight} y2={rowHeight} strokeWidth={0.025 * rowHeight} fill={configStyle.colorLinesGrid} /> </g> )} </React.Fragment> ))} {dataModel.pageData.hyperEdgeRanges.map((hyperEdgeRange, indexHyperEdgeRange) => ( <React.Fragment key={`fragment-${indexHyperEdgeRange}`}> <g className={'hyperEdgeBlock hyperEdge-col-' + indexHyperEdgeRange} key={'hyperEdgeBlockddd hyperEdge-col-' + indexHyperEdgeRange} onMouseEnter={onMouseEnterHyperEdge} onMouseLeave={onMouseLeaveHyperEdge} > <g key={'groupBlockExtra-' + indexHyperEdgeRange} transform={`translate(${rowLabelColumnWidth + indexHyperEdgeRange * rowHeight + 0.5 * rowHeight},${yOffset + 0.5 * rowHeight})`} > {currentPageRows && dataLinesHyperedges[indexHyperEdgeRange]?.valid && ( <CustomLine key={'hyperRangeBlockLine line_' + indexHyperEdgeRange} x1={0} y1={(dataLinesHyperedges[indexHyperEdgeRange].y0 - currentPageRows.startIndexRow) * rowHeight} x2={0} y2={(dataLinesHyperedges[indexHyperEdgeRange].y1 - currentPageRows.startIndexRow) * rowHeight} strokeWidth={0.075 * rowHeight} fill={visualizationColors.GPNeutral.colors[1][0]} /> )} </g> <g className={'hyperEdgeBlockCircles'} key={'hyperEdgeBlockCircles-' + indexHyperEdgeRange} transform={`translate(${rowLabelColumnWidth + indexHyperEdgeRange * rowHeight + 0.5 * rowHeight},${yOffset + 0.5 * rowHeight})`} > {currentPageRows && hyperEdgeRange.hyperEdges.indices .filter((valueIndex) => valueIndex >= currentPageRows.startIndexRow && valueIndex < currentPageRows.endIndexRow) .map((valueIndex: number, indexColumn: number) => ( <circle key={'circleBlock-' + valueIndex + '_' + indexColumn} className={`circle-${valueIndex}`} cx={0} cy={(valueIndex - currentPageRows.startIndexRow) * rowHeight} r={rowHeight * 0.25} fill="white" strokeWidth={0.025 * rowHeight} stroke="black" /> ))} </g> </g> </React.Fragment> ))} </g> </> ); };