diff --git a/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx b/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx index 82f40a8d8d0ac01a7e6a369f1df79d415ac106fc..873f15a8fc9eeb7ce9d558a836ab4b55485f6b84 100644 --- a/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx +++ b/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from 'react'; import { Table, AugmentedNodeAttributes } from './components/Table'; import { VisualizationPropTypes, VISComponentType, VisualizationSettingsPropTypes } from '../../common'; import { Input } from '@graphpolaris/shared/lib/components/inputs'; @@ -6,6 +6,11 @@ import { SettingsContainer } from '@graphpolaris/shared/lib/vis/components/confi import { Button } from '@graphpolaris/shared/lib/components/buttons'; import { useSearchResultData } from '@graphpolaris/shared/lib/data-access'; import { EntityPill } from '@graphpolaris/shared/lib/components/pills/Pill'; +import html2canvas from 'html2canvas'; + +export interface TableVisHandle { + exportImageInternal: () => void; +} export type TableProps = { id: string; @@ -27,93 +32,114 @@ const settings: TableProps = { maxBarsCount: 10, }; -export const TableVis = ({ data, schema, settings, updateSettings, graphMetadata }: VisualizationPropTypes<TableProps>) => { - const searchResults = useSearchResultData(); - const ref = useRef<HTMLDivElement>(null); - useEffect(() => { - if (graphMetadata != undefined && settings.displayEntity === '') { - if (!graphMetadata.nodes.labels.includes(settings.displayEntity)) { - updateSettings({ - displayEntity: graphMetadata.nodes.labels[0], - displayAttributes: Object.keys(graphMetadata.nodes.types[graphMetadata.nodes.labels[0]].attributes), - }); +export const TableVis = forwardRef<TableVisHandle, VisualizationPropTypes<TableProps>>( + ({ data, schema, settings, updateSettings, graphMetadata }, refExternal) => { + const searchResults = useSearchResultData(); + const ref = useRef<HTMLDivElement>(null); + useEffect(() => { + if (graphMetadata != undefined && settings.displayEntity === '') { + if (!graphMetadata.nodes.labels.includes(settings.displayEntity)) { + updateSettings({ + displayEntity: graphMetadata.nodes.labels[0], + displayAttributes: Object.keys(graphMetadata.nodes.types[graphMetadata.nodes.labels[0]].attributes), + }); + } } - } - }, [graphMetadata, data, settings]); + }, [graphMetadata, data, settings]); + + const attributesArray = useMemo<AugmentedNodeAttributes[]>(() => { + //const similiarityThreshold = 0.9; + let displayAttributesSorted: string[]; + + displayAttributesSorted = [...settings.displayAttributes].sort((a, b) => a.localeCompare(b)); - const attributesArray = useMemo<AugmentedNodeAttributes[]>(() => { - //const similiarityThreshold = 0.9; - let displayAttributesSorted: string[]; + const dataNodes = (searchResults?.nodes?.length ?? 0) === 0 ? data.nodes : searchResults.nodes; - displayAttributesSorted = [...settings.displayAttributes].sort((a, b) => a.localeCompare(b)); + return ( + dataNodes + .filter((node) => { + // some dataset do not have label field + let labelNode = ''; + if (node.label !== undefined) { + labelNode = node.label; + } else { + const idParts = node._id.split('/'); + labelNode = idParts[0]; + } + return labelNode === settings.displayEntity; + }) + ///.filter((obj) => obj.similarity === undefined || obj.similarity >= similiarityThreshold) + .map((node) => { + // get attributes filtered and sorted + const filteredAttributes = Object.fromEntries( + Object.entries(node.attributes) + .filter(([attr]) => settings.displayAttributes.includes(attr)) + .sort(([attrA], [attrB]) => settings.displayAttributes.indexOf(attrA) - settings.displayAttributes.indexOf(attrB)), + ); - const dataNodes = (searchResults?.nodes?.length ?? 0) === 0 ? data.nodes : searchResults.nodes; + // doubled types structure to handle discrepancies in schema object in sb and dev env. + + let types = + schema.nodes.find((n: any) => { + let labelNode = node.label; + return labelNode === n.key; + })?.attributes?.attributes ?? + schema.nodes.find((n: any) => { + let labelNode = node.label; + + return labelNode === n.name; + })?.attributes; + + if (types) { + return { + attribute: filteredAttributes, + type: Object.fromEntries(types.map((t: any) => [t.name, t.type])), + }; + } else { + return { + attribute: filteredAttributes, + type: {}, + }; + } + }) + ); + }, [data.nodes, settings.displayEntity, settings.displayAttributes, searchResults]); + + const exportImageInternal = () => { + if (ref.current) { + // Check if divRef.current is not null + html2canvas(ref.current).then((canvas) => { + const pngData = canvas.toDataURL('image/png'); + const a = document.createElement('a'); + a.href = pngData; + a.download = 'tablevis.png'; + a.click(); + }); + } else { + console.error('The referenced div is null.'); + } + }; + + useImperativeHandle(refExternal, () => ({ + exportImageInternal, + })); return ( - dataNodes - .filter((node) => { - // some dataset do not have label field - let labelNode = ''; - if (node.label !== undefined) { - labelNode = node.label; - } else { - const idParts = node._id.split('/'); - labelNode = idParts[0]; - } - return labelNode === settings.displayEntity; - }) - ///.filter((obj) => obj.similarity === undefined || obj.similarity >= similiarityThreshold) - .map((node) => { - // get attributes filtered and sorted - const filteredAttributes = Object.fromEntries( - Object.entries(node.attributes) - .filter(([attr]) => settings.displayAttributes.includes(attr)) - .sort(([attrA], [attrB]) => settings.displayAttributes.indexOf(attrA) - settings.displayAttributes.indexOf(attrB)), - ); - - // doubled types structure to handle discrepancies in schema object in sb and dev env. - - let types = - schema.nodes.find((n: any) => { - let labelNode = node.label; - return labelNode === n.key; - })?.attributes?.attributes ?? - schema.nodes.find((n: any) => { - let labelNode = node.label; - - return labelNode === n.name; - })?.attributes; - - if (types) { - return { - attribute: filteredAttributes, - type: Object.fromEntries(types.map((t: any) => [t.name, t.type])), - }; - } else { - return { - attribute: filteredAttributes, - type: {}, - }; - } - }) + <div className="h-full w-full" ref={ref}> + {attributesArray.length > 0 && ( + <Table + data={attributesArray} + itemsPerPage={settings.itemsPerPage} + showBarPlot={settings.showBarplot} + showAttributes={settings.displayAttributes} + selectedEntity={settings.displayEntity} + maxBarsCount={settings.maxBarsCount} + /> + )} + </div> ); - }, [data.nodes, settings.displayEntity, settings.displayAttributes, searchResults]); - - return ( - <div className="h-full w-full" ref={ref}> - {attributesArray.length > 0 && ( - <Table - data={attributesArray} - itemsPerPage={settings.itemsPerPage} - showBarPlot={settings.showBarplot} - showAttributes={settings.displayAttributes} - selectedEntity={settings.displayEntity} - maxBarsCount={settings.maxBarsCount} - /> - )} - </div> - ); -}; + }, +); const TableSettings = ({ settings, graphMetadata, updateSettings }: VisualizationSettingsPropTypes<TableProps>) => { useEffect(() => { @@ -218,15 +244,20 @@ const TableSettings = ({ settings, graphMetadata, updateSettings }: Visualizatio </SettingsContainer> ); }; +const tableRef = React.createRef<{ exportImageInternal: () => void }>(); export const TableComponent: VISComponentType<TableProps> = { displayName: 'TableVis', description: 'Node Attribute Statistics and Details', - component: TableVis, + component: React.forwardRef((props: VisualizationPropTypes<TableProps>, ref) => <TableVis {...props} ref={tableRef} />), settingsComponent: TableSettings, settings: settings, exportImage: () => { - alert('Not yet supported'); + if (tableRef.current) { + tableRef.current.exportImageInternal(); + } else { + console.error('Map reference is not set.'); + } }, };