From 1f58267d71879a01c7108a4daa7dc0cf4d9be29d Mon Sep 17 00:00:00 2001 From: Marcos Pieras <pieras.marcos@gmail.com> Date: Wed, 4 Sep 2024 17:08:49 +0000 Subject: [PATCH] Feat/export semantic substrates --- .../semanticsubstratesvis.tsx | 1364 +++++++++-------- 1 file changed, 700 insertions(+), 664 deletions(-) diff --git a/libs/shared/lib/vis/visualizations/semanticsubstratesvis/semanticsubstratesvis.tsx b/libs/shared/lib/vis/visualizations/semanticsubstratesvis/semanticsubstratesvis.tsx index 246e02fe1..4fbd594bb 100644 --- a/libs/shared/lib/vis/visualizations/semanticsubstratesvis/semanticsubstratesvis.tsx +++ b/libs/shared/lib/vis/visualizations/semanticsubstratesvis/semanticsubstratesvis.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState, useMemo, useEffect } from 'react'; +import React, { useRef, useState, useMemo, useEffect, forwardRef, useImperativeHandle } from 'react'; import { Scatterplot, KeyedScatterplotProps } from './components/Scatterplot'; import { SettingsContainer } from '@graphpolaris/shared/lib/vis/components/config'; import { Button } from '@graphpolaris/shared/lib/components/buttons'; @@ -19,6 +19,7 @@ import { DataFromPanel, VisualRegionConfig, } from './components/types'; +import html2canvas from 'html2canvas'; import EdgesLayer, { KeyedEdgesLayerProps } from './components/EdgesLayer'; import { MultiGraph } from 'graphology'; @@ -26,6 +27,10 @@ import { buildGraphology, config, numColorsCategorical, marginAxis, isColorCircl import { SemSubsConfigPanel } from './configPanel'; import { nodeColorHex } from './components/utils'; +export interface VisSemanticSubstratesHandle { + exportImageInternal: () => void; +} + export type SemSubstrProps = { showColor: boolean; dataPanels: DataFromPanel[]; @@ -38,199 +43,149 @@ const settings: SemSubstrProps = { const displayName = 'SemanticSubstratesVis'; -export const VisSemanticSubstrates = ({ data, graphMetadata, settings, updateSettings }: VisualizationPropTypes<SemSubstrProps>) => { - // for sizing the vis - const divRef = useRef<HTMLDivElement>(null); - const [divSize, setDivSize] = useState({ width: 0, height: 0 }); - - // avoid mount iteration - const isMounted = useRef(false); - - // to render scatterplots - const [appState, setAppState] = useState({ - scatterplots: [] as KeyedScatterplotProps[], - dataRegions: [] as RegionData[], - }); - // to render edges layer - const [stateEdges, setStateEdges] = useState({ - edgePlotted: [] as KeyedEdgesLayerProps[], - }); - - // for connecting with config panel - const idScatterplotsFromConfig = useRef<number[]>([]); - const prevDataPanelsRef = useRef<DataFromPanel[]>([]); - - // for preserve brush information - //const idBrushed = useRef<string[][]>([]); - const idBrushed = useRef<{ idScatterplot: number; data: string[] }[]>([]); - - // keep track what is the new scatterplot - const newScatterplotIs = useRef<string>(''); - - // To know the correspondence between scatterplot IDs and edge IDs - const IDScatterplot2IDEdge = useRef<number[]>([]); - const indexNewEdge = useRef<number>(0); - const IDEdgeUpdating = useRef<number>(0); - const IDScatterplotR0 = useRef<number>(0); - const arrayIDScatterplotR0 = useRef<string[]>([]); - - const IDScatterplotUpdating = useRef<number>(0); - // information about edges, useful for brushing - const informationEdges = useRef<{ nameEdges: string[]; idEdges: string[][] }>({ - nameEdges: [], - idEdges: [], - }); - - // information about edges - const arrayConnections = useRef<connectionFromTo[][]>([]); - - // to syncronized the data from scatterplots simulation and create the edges - const [computedData, setComputedData] = useState({ - region1: [] as DataPoint[], // always R0 - region2: [] as DataPoint[], // last scatterplot added - }); - - // data structure to handle node/edge information - const augmentedNodes: AugmentedNodeAttributes[] = useMemo(() => { - return data.nodes.map((node: Node) => ({ - _id: node._id, - attributes: node.attributes, - label: node.label, - })); - }, [data]); - - const augmentedEdges: AugmentedEdgeAttributes[] = useMemo(() => { - return data.edges.map((edge: any) => ({ - id: edge.id, - to: edge.to, - from: edge.from, - attributes: edge.attributes, - label: edge.label, - })); - }, [data]); - - const graphologyGraph: MultiGraph = useMemo( - () => buildGraphology({ nodes: Object.values(augmentedNodes), edges: Object.values(augmentedEdges) }), - [augmentedNodes, augmentedEdges], - ); - - const configVisualRegion = useMemo<VisualRegionConfig>(() => { - const baseConfig = { - marginPercentage: { top: 0.14, right: 0.15, bottom: 0.2, left: 0.15 }, - width: divSize.width, - widthPercentage: 0.4, - height: 300, - }; - - const margin = { - top: baseConfig.marginPercentage.top * baseConfig.height, - right: baseConfig.marginPercentage.right * baseConfig.width, - bottom: baseConfig.marginPercentage.bottom * baseConfig.height, - left: baseConfig.marginPercentage.left * baseConfig.width, - }; - - const widthMargin = baseConfig.width - margin.right - margin.left; - const heightMargin = baseConfig.height - margin.top - margin.bottom; - - return { - ...baseConfig, - margin, - widthMargin, - heightMargin, - }; - }, [divSize]); - - const configVisualEdges = useRef<VisualEdgesConfig>({ - width: divSize.width, - height: configVisualRegion.height, - configRegion: configVisualRegion, - offsetY: 0, - stroke: config.edges.stroke, - strokeWidth: config.edges.strokeWidth, - strokeOpacity: config.edges.strokeOpacity, - }); - - const handleBrushClear = useMemo(() => { - return (selectedElement: string): void => { - let modifiedString: number = +selectedElement.replace('region_', ''); - //idBrushed.current[modifiedString] = []; - - const indexBrushedR0 = idBrushed.current.findIndex((obj) => obj.idScatterplot === modifiedString); - idBrushed.current[indexBrushedR0].data = []; - - informationEdges.current.idEdges.forEach((edgesR0RN, index) => { - if (edgesR0RN.length !== 0) { - //if (idBrushed.current[0]?.length === 0 && idBrushed.current[index + 1]?.length === 0) { - if ( - idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data.length === 0 && - idBrushed.current.find((obj) => obj.idScatterplot === index + 1)?.data.length === 0 - ) { - select(`.edge_region0_to_region_${index + 1}`) - .selectAll('path') - .style('stroke-opacity', 1); - } else { - //const edgesVisible = filterArray(idBrushed.current[0], idBrushed.current[index + 1], edgesR0RN, '_fromto_'); +const VisSemanticSubstrates = forwardRef<VisSemanticSubstratesHandle, VisualizationPropTypes<SemSubstrProps>>( + ({ data, graphMetadata, settings, updateSettings }, refExternal) => { + // for sizing the vis + const divRef = useRef<HTMLDivElement>(null); + const [divSize, setDivSize] = useState({ width: 0, height: 0 }); + + // avoid mount iteration + const isMounted = useRef(false); + + // to render scatterplots + const [appState, setAppState] = useState({ + scatterplots: [] as KeyedScatterplotProps[], + dataRegions: [] as RegionData[], + }); + // to render edges layer + const [stateEdges, setStateEdges] = useState({ + edgePlotted: [] as KeyedEdgesLayerProps[], + }); + + // for connecting with config panel + const idScatterplotsFromConfig = useRef<number[]>([]); + const prevDataPanelsRef = useRef<DataFromPanel[]>([]); + + // for preserve brush information + //const idBrushed = useRef<string[][]>([]); + const idBrushed = useRef<{ idScatterplot: number; data: string[] }[]>([]); + + // keep track what is the new scatterplot + const newScatterplotIs = useRef<string>(''); + + // To know the correspondence between scatterplot IDs and edge IDs + const IDScatterplot2IDEdge = useRef<number[]>([]); + const indexNewEdge = useRef<number>(0); + const IDEdgeUpdating = useRef<number>(0); + const IDScatterplotR0 = useRef<number>(0); + const arrayIDScatterplotR0 = useRef<string[]>([]); + + const IDScatterplotUpdating = useRef<number>(0); + // information about edges, useful for brushing + const informationEdges = useRef<{ nameEdges: string[]; idEdges: string[][] }>({ + nameEdges: [], + idEdges: [], + }); + + // information about edges + const arrayConnections = useRef<connectionFromTo[][]>([]); + + // to syncronized the data from scatterplots simulation and create the edges + const [computedData, setComputedData] = useState({ + region1: [] as DataPoint[], // always R0 + region2: [] as DataPoint[], // last scatterplot added + }); + + // data structure to handle node/edge information + const augmentedNodes: AugmentedNodeAttributes[] = useMemo(() => { + return data.nodes.map((node: Node) => ({ + _id: node._id, + attributes: node.attributes, + label: node.label, + })); + }, [data]); + + const augmentedEdges: AugmentedEdgeAttributes[] = useMemo(() => { + return data.edges.map((edge: any) => ({ + id: edge.id, + to: edge.to, + from: edge.from, + attributes: edge.attributes, + label: edge.label, + })); + }, [data]); + + const graphologyGraph: MultiGraph = useMemo( + () => buildGraphology({ nodes: Object.values(augmentedNodes), edges: Object.values(augmentedEdges) }), + [augmentedNodes, augmentedEdges], + ); + + const configVisualRegion = useMemo<VisualRegionConfig>(() => { + const baseConfig = { + marginPercentage: { top: 0.14, right: 0.15, bottom: 0.2, left: 0.15 }, + width: divSize.width, + widthPercentage: 0.4, + height: 300, + }; - const edgesVisible = filterArray( - idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data ?? [], - idBrushed.current.find((obj) => obj.idScatterplot === index + 1)?.data ?? [], - edgesR0RN, - '_fromto_', - ); + const margin = { + top: baseConfig.marginPercentage.top * baseConfig.height, + right: baseConfig.marginPercentage.right * baseConfig.width, + bottom: baseConfig.marginPercentage.bottom * baseConfig.height, + left: baseConfig.marginPercentage.left * baseConfig.width, + }; - const edgesVisibleSlash = edgesVisible.map((element) => element.replace(/\//g, '_')); + const widthMargin = baseConfig.width - margin.right - margin.left; + const heightMargin = baseConfig.height - margin.top - margin.bottom; - const edgesVisibleSlashNotNum = edgesVisibleSlash.map((item) => { - if (/^\d/.test(item)) { - return 'idAdd_' + item; - } else { - return item; - } - }); + return { + ...baseConfig, + margin, + widthMargin, + heightMargin, + }; + }, [divSize]); - const edgesVisibleSlashDot: string = edgesVisibleSlashNotNum.map((edgeClass) => `.${edgeClass}`).join(','); - select(`.edge_region0_to_region_${index + 1}`) - .selectAll('path') - .style('stroke-opacity', 0); - selectAll(edgesVisibleSlashDot).style('stroke-opacity', 1); - } - } - }); - }; - }, []); - - const handleBrushUpdate = useMemo(() => { - return (idElements: string[], selectedElement: string): void => { - let modifiedString: number = +selectedElement.replace('region_', ''); - - //idBrushed.current[modifiedString] = idElements; - const indexBrushedR0 = idBrushed.current.findIndex((obj) => obj.idScatterplot === modifiedString); - idBrushed.current[indexBrushedR0].data = idElements; - - // iterate over the region pairs: r0-r1, r0-r2, and update visibility of edges based on registered brushed ids - informationEdges.current.idEdges.forEach((edgesR0RN, index) => { - if (modifiedString == 0) { - //if (idBrushed.current[0].length == 0 && idBrushed.current[index + 1].length == 0) { - - if ( - idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data.length === 0 && - idBrushed.current.find((obj) => obj.idScatterplot === index + 1)?.data.length === 0 - ) { - select(`.edge_region0_to_region_${index + 1}`) - .selectAll('path') - .style('stroke-opacity', 0); - } else { - //const edgesVisible = filterArray(idBrushed.current[0], idBrushed.current[index + 1], edgesR0RN, '_fromto_'); + const configVisualEdges = useRef<VisualEdgesConfig>({ + width: divSize.width, + height: configVisualRegion.height, + configRegion: configVisualRegion, + offsetY: 0, + stroke: config.edges.stroke, + strokeWidth: config.edges.strokeWidth, + strokeOpacity: config.edges.strokeOpacity, + }); + + const handleBrushClear = useMemo(() => { + return (selectedElement: string): void => { + let modifiedString: number = +selectedElement.replace('region_', ''); + //idBrushed.current[modifiedString] = []; + + const indexBrushedR0 = idBrushed.current.findIndex((obj) => obj.idScatterplot === modifiedString); + idBrushed.current[indexBrushedR0].data = []; + + informationEdges.current.idEdges.forEach((edgesR0RN, index) => { + if (edgesR0RN.length !== 0) { + //if (idBrushed.current[0]?.length === 0 && idBrushed.current[index + 1]?.length === 0) { + if ( + idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data.length === 0 && + idBrushed.current.find((obj) => obj.idScatterplot === index + 1)?.data.length === 0 + ) { + select(`.edge_region0_to_region_${index + 1}`) + .selectAll('path') + .style('stroke-opacity', 1); + } else { + //const edgesVisible = filterArray(idBrushed.current[0], idBrushed.current[index + 1], edgesR0RN, '_fromto_'); - const edgesVisible = filterArray( - idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data ?? [], - idBrushed.current.find((obj) => obj.idScatterplot === index + 1)?.data ?? [], - edgesR0RN, - '_fromto_', - ); + const edgesVisible = filterArray( + idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data ?? [], + idBrushed.current.find((obj) => obj.idScatterplot === index + 1)?.data ?? [], + edgesR0RN, + '_fromto_', + ); - if (edgesVisible.length != 0) { const edgesVisibleSlash = edgesVisible.map((element) => element.replace(/\//g, '_')); + const edgesVisibleSlashNotNum = edgesVisibleSlash.map((item) => { if (/^\d/.test(item)) { return 'idAdd_' + item; @@ -244,225 +199,186 @@ export const VisSemanticSubstrates = ({ data, graphMetadata, settings, updateSet .selectAll('path') .style('stroke-opacity', 0); selectAll(edgesVisibleSlashDot).style('stroke-opacity', 1); - } else { - select(`.edge_region0_to_region_${index + 1}`) - .selectAll('path') - .style('stroke-opacity', 0); } } - } else if (modifiedString == index + 1) { - //if (idBrushed.current[0].length == 0 && idBrushed.current[index + 1].length == 0) { - - if ( - idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data.length === 0 && - idBrushed.current.find((obj) => obj.idScatterplot === index + 1)?.data.length === 0 - ) { - select(`.edge_region0_to_region_${index + 1}`) - .selectAll('path') - .style('stroke-opacity', 0); - } else { - //const edgesVisible = filterArray(idBrushed.current[0], idBrushed.current[index + 1], edgesR0RN, '_fromto_'); - const edgesVisible = filterArray( - idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data ?? [], - idBrushed.current.find((obj) => obj.idScatterplot === index + 1)?.data ?? [], - edgesR0RN, - '_fromto_', - ); - if (edgesVisible.length != 0) { - const edgesVisibleSlash = edgesVisible.map((element) => element.replace(/\//g, '_')); - const edgesVisibleSlashNotNum = edgesVisibleSlash.map((item) => { - if (/^\d/.test(item)) { - return 'idAdd_' + item; - } else { - return item; - } - }); + }); + }; + }, []); - const edgesVisibleSlashDot: string = edgesVisibleSlashNotNum.map((edgeClass) => `.${edgeClass}`).join(','); + const handleBrushUpdate = useMemo(() => { + return (idElements: string[], selectedElement: string): void => { + let modifiedString: number = +selectedElement.replace('region_', ''); + + //idBrushed.current[modifiedString] = idElements; + const indexBrushedR0 = idBrushed.current.findIndex((obj) => obj.idScatterplot === modifiedString); + idBrushed.current[indexBrushedR0].data = idElements; + // iterate over the region pairs: r0-r1, r0-r2, and update visibility of edges based on registered brushed ids + informationEdges.current.idEdges.forEach((edgesR0RN, index) => { + if (modifiedString == 0) { + //if (idBrushed.current[0].length == 0 && idBrushed.current[index + 1].length == 0) { + + if ( + idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data.length === 0 && + idBrushed.current.find((obj) => obj.idScatterplot === index + 1)?.data.length === 0 + ) { select(`.edge_region0_to_region_${index + 1}`) .selectAll('path') .style('stroke-opacity', 0); - selectAll(edgesVisibleSlashDot).style('stroke-opacity', 1); } else { + //const edgesVisible = filterArray(idBrushed.current[0], idBrushed.current[index + 1], edgesR0RN, '_fromto_'); + + const edgesVisible = filterArray( + idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data ?? [], + idBrushed.current.find((obj) => obj.idScatterplot === index + 1)?.data ?? [], + edgesR0RN, + '_fromto_', + ); + + if (edgesVisible.length != 0) { + const edgesVisibleSlash = edgesVisible.map((element) => element.replace(/\//g, '_')); + const edgesVisibleSlashNotNum = edgesVisibleSlash.map((item) => { + if (/^\d/.test(item)) { + return 'idAdd_' + item; + } else { + return item; + } + }); + + const edgesVisibleSlashDot: string = edgesVisibleSlashNotNum.map((edgeClass) => `.${edgeClass}`).join(','); + select(`.edge_region0_to_region_${index + 1}`) + .selectAll('path') + .style('stroke-opacity', 0); + selectAll(edgesVisibleSlashDot).style('stroke-opacity', 1); + } else { + select(`.edge_region0_to_region_${index + 1}`) + .selectAll('path') + .style('stroke-opacity', 0); + } + } + } else if (modifiedString == index + 1) { + //if (idBrushed.current[0].length == 0 && idBrushed.current[index + 1].length == 0) { + + if ( + idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data.length === 0 && + idBrushed.current.find((obj) => obj.idScatterplot === index + 1)?.data.length === 0 + ) { select(`.edge_region0_to_region_${index + 1}`) .selectAll('path') .style('stroke-opacity', 0); + } else { + //const edgesVisible = filterArray(idBrushed.current[0], idBrushed.current[index + 1], edgesR0RN, '_fromto_'); + const edgesVisible = filterArray( + idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data ?? [], + idBrushed.current.find((obj) => obj.idScatterplot === index + 1)?.data ?? [], + edgesR0RN, + '_fromto_', + ); + if (edgesVisible.length != 0) { + const edgesVisibleSlash = edgesVisible.map((element) => element.replace(/\//g, '_')); + const edgesVisibleSlashNotNum = edgesVisibleSlash.map((item) => { + if (/^\d/.test(item)) { + return 'idAdd_' + item; + } else { + return item; + } + }); + + const edgesVisibleSlashDot: string = edgesVisibleSlashNotNum.map((edgeClass) => `.${edgeClass}`).join(','); + + select(`.edge_region0_to_region_${index + 1}`) + .selectAll('path') + .style('stroke-opacity', 0); + selectAll(edgesVisibleSlashDot).style('stroke-opacity', 1); + } else { + select(`.edge_region0_to_region_${index + 1}`) + .selectAll('path') + .style('stroke-opacity', 0); + } } } - } - }); - }; - }, []); - - const handleResultJitter = (data: DataPoint[], idScatterplot: number) => { - //console.log('DONE JITTER ', idScatterplot); - if (idScatterplot == IDScatterplotR0.current) { - setComputedData((prevData) => ({ - ...prevData, - region1: data, - })); - } else { - setComputedData((prevData) => ({ - ...prevData, - region2: data, - })); - } - }; - - useEffect(() => { - function handleResize() { - if (divRef.current) { - setDivSize({ width: divRef.current.getBoundingClientRect().width, height: divRef.current.getBoundingClientRect().height }); + }); + }; + }, []); + + const handleResultJitter = (data: DataPoint[], idScatterplot: number) => { + //console.log('DONE JITTER ', idScatterplot); + if (idScatterplot == IDScatterplotR0.current) { + setComputedData((prevData) => ({ + ...prevData, + region1: data, + })); + } else { + setComputedData((prevData) => ({ + ...prevData, + region2: data, + })); } - } - - window.addEventListener('resize', handleResize); - if (divRef.current) new ResizeObserver(handleResize).observe(divRef.current); - - return () => { - window.removeEventListener('resize', handleResize); }; - }, []); - useEffect(() => { - if (idBrushed.current.length != 0) { - if (idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data.length != 0) { - handleBrushUpdate(idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data ?? [], 'region_0'); + useEffect(() => { + function handleResize() { + if (divRef.current) { + setDivSize({ width: divRef.current.getBoundingClientRect().width, height: divRef.current.getBoundingClientRect().height }); + } } - } - }, [stateEdges]); - // manages the addition of edges elements - useEffect(() => { - //console.log('computedData', computedData.region1.length > 0 && computedData.region2.length > 0, computedData, newScatterplotIs.current); - if (computedData.region1.length > 0 && computedData.region2.length > 0) { - if (newScatterplotIs.current === 'add' || !stateEdges.edgePlotted[IDEdgeUpdating.current]) { - // !FIXME !stateEdges.edgePlotted[IDEdgeUpdating.current] was added to solve bug, but now only last scatter shows edges - - //console.log('ADD EDGES '); - const temporalConfigVisualEdges: VisualEdgesConfig = { - ...configVisualEdges.current, - height: configVisualRegion.height * appState.scatterplots.length, - offsetY: configVisualRegion.height * (appState.scatterplots.length - 2), - }; - - const visualConfigRef = { current: temporalConfigVisualEdges } as React.MutableRefObject<VisualEdgesConfig>; - - const newEdgePlot: KeyedEdgesLayerProps = { - key: appState.scatterplots.length.toString(), - dataConnections: arrayConnections.current[indexNewEdge.current - 1], - visualConfig: visualConfigRef, - visualScatterplot: configVisualRegion, - data1: computedData.region1, - data2: computedData.region2, - nameEdges: informationEdges.current.nameEdges[indexNewEdge.current - 1], - }; + window.addEventListener('resize', handleResize); + if (divRef.current) new ResizeObserver(handleResize).observe(divRef.current); - //console.log('newEdgePlot', newEdgePlot); - setStateEdges({ - edgePlotted: [...stateEdges.edgePlotted, newEdgePlot], - }); + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); - // update edges - } else if (IDScatterplotUpdating.current === IDScatterplotR0.current) { - //console.log('UPDATE EDGES of R0 '); - const stateEdgesTemporal = cloneDeep(stateEdges.edgePlotted); + useEffect(() => { + if (idBrushed.current.length != 0) { + if (idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data.length != 0) { + handleBrushUpdate(idBrushed.current.find((obj) => obj.idScatterplot === 0)?.data ?? [], 'region_0'); + } + } + }, [stateEdges]); - const modifiedStateEdges = stateEdgesTemporal.map((obj, index) => { - const data1 = (stateEdgesTemporal[index].data1 = computedData.region1); - const dataConnections = arrayConnections.current[index]; + // manages the addition of edges elements + useEffect(() => { + //console.log('computedData', computedData.region1.length > 0 && computedData.region2.length > 0, computedData, newScatterplotIs.current); + if (computedData.region1.length > 0 && computedData.region2.length > 0) { + if (newScatterplotIs.current === 'add' || !stateEdges.edgePlotted[IDEdgeUpdating.current]) { + // !FIXME !stateEdges.edgePlotted[IDEdgeUpdating.current] was added to solve bug, but now only last scatter shows edges + //console.log('ADD EDGES '); const temporalConfigVisualEdges: VisualEdgesConfig = { ...configVisualEdges.current, - height: configVisualRegion.height * (index + 2), - offsetY: configVisualRegion.height * index, + height: configVisualRegion.height * appState.scatterplots.length, + offsetY: configVisualRegion.height * (appState.scatterplots.length - 2), }; const visualConfigRef = { current: temporalConfigVisualEdges } as React.MutableRefObject<VisualEdgesConfig>; - return { - ...obj, + const newEdgePlot: KeyedEdgesLayerProps = { + key: appState.scatterplots.length.toString(), + dataConnections: arrayConnections.current[indexNewEdge.current - 1], visualConfig: visualConfigRef, - data1, - dataConnections, + visualScatterplot: configVisualRegion, + data1: computedData.region1, + data2: computedData.region2, + nameEdges: informationEdges.current.nameEdges[indexNewEdge.current - 1], }; - }); - - setStateEdges({ - edgePlotted: modifiedStateEdges, - }); - } else if (stateEdges.edgePlotted[IDEdgeUpdating.current]) { - //console.log('UPDATE EDGES NO R0'); - - //console.log('informationEdges.current.nameEdges ', informationEdges.current.nameEdges); - const stateEdgesTemporal = cloneDeep(stateEdges.edgePlotted); - - //console.log('IDEdgeUpdating', IDEdgeUpdating.current, stateEdges.edgePlotted, stateEdgesTemporal[IDEdgeUpdating.current]); - stateEdgesTemporal[IDEdgeUpdating.current].data1 = computedData.region1; // necessary ? - stateEdgesTemporal[IDEdgeUpdating.current].data2 = computedData.region2; - stateEdgesTemporal[IDEdgeUpdating.current].dataConnections = arrayConnections.current[IDEdgeUpdating.current]; - setStateEdges({ - edgePlotted: stateEdgesTemporal, - }); - } else { - console.error('Error: Edge not found', IDScatterplotUpdating.current, stateEdges.edgePlotted); - } - } - }, [computedData.region2, computedData.region1]); - - function createNewScatterplot(newPanel: DataFromPanel) { - //console.log('createNewScatterplot:', newPanel, 'IDScatterplotR0: ', IDScatterplotR0.current); - if (idScatterplotsFromConfig.current.length === 0 && !IDScatterplotR0.current) { - IDScatterplotR0.current = newPanel.id; - //console.log('newPanel ', newPanel); - } - - idScatterplotsFromConfig.current.push(newPanel.id); - newScatterplotIs.current = 'add'; - return handleNewDataRegion(newPanel.data, newPanel.id); - } - - function removeScatterplot(deletedPanel?: DataFromPanel) { - if (deletedPanel) { - // checks if deleted one is R0 - if (deletedPanel.id === IDScatterplotR0.current) { - // !FIXME: dont allow delete of R0 - /* - setComputedData((prevState) => ({ - ...prevState, - region1: [], - })); - */ - } else { - // checks if deleted one is RX - - // Delete scatterplot and associated variables - idScatterplotsFromConfig.current = idScatterplotsFromConfig.current.filter((item) => item !== deletedPanel.id); - setAppState((prevState) => ({ - scatterplots: prevState.scatterplots.filter((scatterplot) => scatterplot.key !== deletedPanel.id), - dataRegions: prevState.dataRegions.filter((_, index) => prevState.scatterplots[index].key !== deletedPanel.id), - })); - - idBrushed.current = idBrushed.current.filter((item) => item.idScatterplot !== deletedPanel.id); - - // Delete edges - const removeIndexEdge = informationEdges.current.nameEdges.findIndex( - (name) => name === `edge_region0_to_region_${deletedPanel.id}`, - ); - const stateEdgesTemporal = cloneDeep(stateEdges.edgePlotted); - const stateEdgesTemporalFiltered = stateEdgesTemporal.filter((_, index) => index !== removeIndexEdge); + //console.log('newEdgePlot', newEdgePlot); + setStateEdges({ + edgePlotted: [...stateEdges.edgePlotted, newEdgePlot], + }); - if (removeIndexEdge != -1) { - // check if it need it - IDScatterplot2IDEdge.current = IDScatterplot2IDEdge.current.filter((_, index) => index !== removeIndexEdge); + // update edges + } else if (IDScatterplotUpdating.current === IDScatterplotR0.current) { + //console.log('UPDATE EDGES of R0 '); + const stateEdgesTemporal = cloneDeep(stateEdges.edgePlotted); - informationEdges.current.idEdges = informationEdges.current.idEdges.filter((_, index) => index !== removeIndexEdge); - informationEdges.current.nameEdges = informationEdges.current.nameEdges.filter((_, index) => index !== removeIndexEdge); - arrayConnections.current = arrayConnections.current.filter((_, index) => index !== removeIndexEdge); + const modifiedStateEdges = stateEdgesTemporal.map((obj, index) => { + const data1 = (stateEdgesTemporal[index].data1 = computedData.region1); + const dataConnections = arrayConnections.current[index]; - const modifiedStateEdges = stateEdgesTemporalFiltered.map((obj, index) => { const temporalConfigVisualEdges: VisualEdgesConfig = { ...configVisualEdges.current, height: configVisualRegion.height * (index + 2), @@ -474,359 +390,471 @@ export const VisSemanticSubstrates = ({ data, graphMetadata, settings, updateSet return { ...obj, visualConfig: visualConfigRef, + data1, + dataConnections, }; }); setStateEdges({ edgePlotted: modifiedStateEdges, }); + } else if (stateEdges.edgePlotted[IDEdgeUpdating.current]) { + //console.log('UPDATE EDGES NO R0'); + + //console.log('informationEdges.current.nameEdges ', informationEdges.current.nameEdges); + + const stateEdgesTemporal = cloneDeep(stateEdges.edgePlotted); + + //console.log('IDEdgeUpdating', IDEdgeUpdating.current, stateEdges.edgePlotted, stateEdgesTemporal[IDEdgeUpdating.current]); + stateEdgesTemporal[IDEdgeUpdating.current].data1 = computedData.region1; // necessary ? + stateEdgesTemporal[IDEdgeUpdating.current].data2 = computedData.region2; + stateEdgesTemporal[IDEdgeUpdating.current].dataConnections = arrayConnections.current[IDEdgeUpdating.current]; + setStateEdges({ + edgePlotted: stateEdgesTemporal, + }); + } else { + console.error('Error: Edge not found', IDScatterplotUpdating.current, stateEdges.edgePlotted); } + } + }, [computedData.region2, computedData.region1]); - indexNewEdge.current--; + function createNewScatterplot(newPanel: DataFromPanel) { + //console.log('createNewScatterplot:', newPanel, 'IDScatterplotR0: ', IDScatterplotR0.current); + if (idScatterplotsFromConfig.current.length === 0 && !IDScatterplotR0.current) { + IDScatterplotR0.current = newPanel.id; + //console.log('newPanel ', newPanel); } + + idScatterplotsFromConfig.current.push(newPanel.id); + newScatterplotIs.current = 'add'; + return handleNewDataRegion(newPanel.data, newPanel.id); } - } - - function updateScatterplot(updatedPanel?: DataFromPanel) { - if (updatedPanel) { - //console.log('updatedPanel', updatedPanel, appState.scatterplots); - - const idScatterplotUpdate = appState.scatterplots.findIndex((item) => item.key === updatedPanel.id); - newScatterplotIs.current = 'update'; - if (idScatterplotUpdate !== -1) { - //handleBrushClear(`region_${indexOnState}`); - - if (!updatedPanel.data.entitySelected) return; - //console.log('UPDATE: appState ', idScatterplotUpdate); - - const regionUserSelection: UserSelection = { - name: appState.dataRegions[idScatterplotUpdate].name, - nodeName: updatedPanel.data.entitySelected, - attributeAsRegion: updatedPanel.data.attributeSelected, - attributeAsRegionSelection: updatedPanel.data.attributeValueSelected, - placement: { - xAxis: updatedPanel.data.xAxisSelected, - yAxis: updatedPanel.data.yAxisSelected, - colorNodes: appState.dataRegions[idScatterplotUpdate].colorNodes, - colorNodesStroke: appState.dataRegions[idScatterplotUpdate].colorNodesStroke, - colorFillBrush: config.brush.fillClr, - colorStrokeBrush: config.brush.strokeClr, - }, - }; - const regionDataUser: RegionData = getRegionData(augmentedNodes, regionUserSelection); + function removeScatterplot(deletedPanel?: DataFromPanel) { + if (deletedPanel) { + // checks if deleted one is R0 + if (deletedPanel.id === IDScatterplotR0.current) { + // !FIXME: dont allow delete of R0 + /* + setComputedData((prevState) => ({ + ...prevState, + region1: [], + })); + */ + } else { + // checks if deleted one is RX + + // Delete scatterplot and associated variables + idScatterplotsFromConfig.current = idScatterplotsFromConfig.current.filter((item) => item !== deletedPanel.id); + setAppState((prevState) => ({ + scatterplots: prevState.scatterplots.filter((scatterplot) => scatterplot.key !== deletedPanel.id), + dataRegions: prevState.dataRegions.filter((_, index) => prevState.scatterplots[index].key !== deletedPanel.id), + })); - let xScaleShared: any; - let yScaleShared: any; + idBrushed.current = idBrushed.current.filter((item) => item.idScatterplot !== deletedPanel.id); - if (!regionDataUser.xAxisName && !regionDataUser.yAxisName) { - xScaleShared = noDataRange; - yScaleShared = noDataRange; - } else if (regionDataUser.xAxisName && regionDataUser.yAxisName) { - if (typeof regionDataUser.xData[0] != 'number') { - const xExtent = getUniqueValues(regionDataUser.xData); - xScaleShared = xExtent; - } else { - const xExtent: [number, number] = setExtension(marginAxis, [...regionDataUser.xData, ...regionDataUser.xData]); - xScaleShared = xExtent; - } + // Delete edges + const removeIndexEdge = informationEdges.current.nameEdges.findIndex( + (name) => name === `edge_region0_to_region_${deletedPanel.id}`, + ); + const stateEdgesTemporal = cloneDeep(stateEdges.edgePlotted); + const stateEdgesTemporalFiltered = stateEdgesTemporal.filter((_, index) => index !== removeIndexEdge); - if (typeof regionDataUser.yData[0] != 'number') { - const yExtent = getUniqueValues(regionDataUser.yData); - yScaleShared = yExtent; - } else { - const yExtent: [number, number] = setExtension(marginAxis, [...regionDataUser.yData, ...regionDataUser.yData]); - yScaleShared = yExtent; - } - } else if (regionDataUser.xAxisName) { - if (typeof regionDataUser.xData[0] != 'number') { - const xExtent = getUniqueValues(regionDataUser.xData); - xScaleShared = xExtent; + if (removeIndexEdge != -1) { + // check if it need it + IDScatterplot2IDEdge.current = IDScatterplot2IDEdge.current.filter((_, index) => index !== removeIndexEdge); - yScaleShared = noDataRange; - } else { - const xExtent: string[] | number[] = setExtension(marginAxis, [...regionDataUser.xData, ...regionDataUser.xData]); - xScaleShared = xExtent; + informationEdges.current.idEdges = informationEdges.current.idEdges.filter((_, index) => index !== removeIndexEdge); + informationEdges.current.nameEdges = informationEdges.current.nameEdges.filter((_, index) => index !== removeIndexEdge); + arrayConnections.current = arrayConnections.current.filter((_, index) => index !== removeIndexEdge); - yScaleShared = noDataRange; + const modifiedStateEdges = stateEdgesTemporalFiltered.map((obj, index) => { + const temporalConfigVisualEdges: VisualEdgesConfig = { + ...configVisualEdges.current, + height: configVisualRegion.height * (index + 2), + offsetY: configVisualRegion.height * index, + }; + + const visualConfigRef = { current: temporalConfigVisualEdges } as React.MutableRefObject<VisualEdgesConfig>; + + return { + ...obj, + visualConfig: visualConfigRef, + }; + }); + + setStateEdges({ + edgePlotted: modifiedStateEdges, + }); } - } else if (regionDataUser.yAxisName) { - if (typeof regionDataUser.yData[0] != 'number') { - const yExtent = getUniqueValues(regionDataUser.yData); - yScaleShared = yExtent; - xScaleShared = noDataRange; - } else { - const yExtent: [number, number] = setExtension(marginAxis, [...regionDataUser.yData, ...regionDataUser.yData]); + indexNewEdge.current--; + } + } + } - yScaleShared = yExtent; + function updateScatterplot(updatedPanel?: DataFromPanel) { + if (updatedPanel) { + //console.log('updatedPanel', updatedPanel, appState.scatterplots); + + const idScatterplotUpdate = appState.scatterplots.findIndex((item) => item.key === updatedPanel.id); + newScatterplotIs.current = 'update'; + if (idScatterplotUpdate !== -1) { + //handleBrushClear(`region_${indexOnState}`); + + if (!updatedPanel.data.entitySelected) return; + //console.log('UPDATE: appState ', idScatterplotUpdate); + + const regionUserSelection: UserSelection = { + name: appState.dataRegions[idScatterplotUpdate].name, + nodeName: updatedPanel.data.entitySelected, + attributeAsRegion: updatedPanel.data.attributeSelected, + attributeAsRegionSelection: updatedPanel.data.attributeValueSelected, + placement: { + xAxis: updatedPanel.data.xAxisSelected, + yAxis: updatedPanel.data.yAxisSelected, + colorNodes: appState.dataRegions[idScatterplotUpdate].colorNodes, + colorNodesStroke: appState.dataRegions[idScatterplotUpdate].colorNodesStroke, + colorFillBrush: config.brush.fillClr, + colorStrokeBrush: config.brush.strokeClr, + }, + }; + const regionDataUser: RegionData = getRegionData(augmentedNodes, regionUserSelection); + + let xScaleShared: any; + let yScaleShared: any; + + if (!regionDataUser.xAxisName && !regionDataUser.yAxisName) { xScaleShared = noDataRange; + yScaleShared = noDataRange; + } else if (regionDataUser.xAxisName && regionDataUser.yAxisName) { + if (typeof regionDataUser.xData[0] != 'number') { + const xExtent = getUniqueValues(regionDataUser.xData); + xScaleShared = xExtent; + } else { + const xExtent: [number, number] = setExtension(marginAxis, [...regionDataUser.xData, ...regionDataUser.xData]); + xScaleShared = xExtent; + } + + if (typeof regionDataUser.yData[0] != 'number') { + const yExtent = getUniqueValues(regionDataUser.yData); + yScaleShared = yExtent; + } else { + const yExtent: [number, number] = setExtension(marginAxis, [...regionDataUser.yData, ...regionDataUser.yData]); + yScaleShared = yExtent; + } + } else if (regionDataUser.xAxisName) { + if (typeof regionDataUser.xData[0] != 'number') { + const xExtent = getUniqueValues(regionDataUser.xData); + xScaleShared = xExtent; + + yScaleShared = noDataRange; + } else { + const xExtent: string[] | number[] = setExtension(marginAxis, [...regionDataUser.xData, ...regionDataUser.xData]); + xScaleShared = xExtent; + + yScaleShared = noDataRange; + } + } else if (regionDataUser.yAxisName) { + if (typeof regionDataUser.yData[0] != 'number') { + const yExtent = getUniqueValues(regionDataUser.yData); + yScaleShared = yExtent; + + xScaleShared = noDataRange; + } else { + const yExtent: [number, number] = setExtension(marginAxis, [...regionDataUser.yData, ...regionDataUser.yData]); + + yScaleShared = yExtent; + + xScaleShared = noDataRange; + } } - } - const updatedScatterplot = { - key: appState.scatterplots[idScatterplotUpdate].key, - data: regionDataUser, - visualConfig: configVisualRegion, - xScaleRange: xScaleShared, - yScaleRange: yScaleShared, - onBrushUpdate: handleBrushUpdate, - onResultJitter: handleResultJitter, - onBrushClear: handleBrushClear, - }; + const updatedScatterplot = { + key: appState.scatterplots[idScatterplotUpdate].key, + data: regionDataUser, + visualConfig: configVisualRegion, + xScaleRange: xScaleShared, + yScaleRange: yScaleShared, + onBrushUpdate: handleBrushUpdate, + onResultJitter: handleResultJitter, + onBrushClear: handleBrushClear, + }; - // modify scatterplot - setAppState((prevState) => { - const scatterplots = [...prevState.scatterplots]; - scatterplots[idScatterplotUpdate] = updatedScatterplot; - return { ...prevState, scatterplots }; - }); + // modify scatterplot + setAppState((prevState) => { + const scatterplots = [...prevState.scatterplots]; + scatterplots[idScatterplotUpdate] = updatedScatterplot; + return { ...prevState, scatterplots }; + }); - // modify edge layer - const arrayIDs = regionDataUser.idData.map((item) => item); - IDScatterplotUpdating.current = idScatterplotUpdate; - //console.log('IDScatterplotUpdating', IDScatterplotUpdating.current, IDScatterplotR0.current, idScatterplotUpdate); - if (idScatterplotUpdate === IDScatterplotR0.current) { - const stateEdgesTemporal = cloneDeep(stateEdges.edgePlotted); + // modify edge layer + const arrayIDs = regionDataUser.idData.map((item) => item); + IDScatterplotUpdating.current = idScatterplotUpdate; + //console.log('IDScatterplotUpdating', IDScatterplotUpdating.current, IDScatterplotR0.current, idScatterplotUpdate); + if (idScatterplotUpdate === IDScatterplotR0.current) { + const stateEdgesTemporal = cloneDeep(stateEdges.edgePlotted); + + stateEdgesTemporal.forEach((obj, index) => { + const IDsConnectedScatterplot = obj.data2.map((value) => value.id); + const [connectedD, connectedD2] = findConnectionsNodes( + arrayIDs, + IDsConnectedScatterplot, + graphologyGraph, + regionDataUser.label, + true, + ); - stateEdgesTemporal.forEach((obj, index) => { - const IDsConnectedScatterplot = obj.data2.map((value) => value.id); + informationEdges.current.idEdges[index] = connectedD2; + arrayConnections.current[index] = connectedD; + }); + } else { + // get indices to modify + const IDEdgeBasedOnIDscatterplot = IDScatterplot2IDEdge.current[idScatterplotUpdate - 1]; + IDEdgeUpdating.current = IDEdgeBasedOnIDscatterplot; + + const indexEdgeInvolved = informationEdges.current.nameEdges.findIndex( + (name) => name === `edge_region0_to_region_${idScatterplotUpdate}`, + ); + //console.log('edge involved: ', IDEdgeBasedOnIDscatterplot, indexEdgeInvolved); + // modify data structures const [connectedD, connectedD2] = findConnectionsNodes( arrayIDs, - IDsConnectedScatterplot, + appState.dataRegions[IDScatterplotR0.current].idData, graphologyGraph, regionDataUser.label, - true, ); - informationEdges.current.idEdges[index] = connectedD2; - arrayConnections.current[index] = connectedD; - }); + informationEdges.current.idEdges[IDEdgeBasedOnIDscatterplot] = connectedD2; + arrayConnections.current[IDEdgeBasedOnIDscatterplot] = connectedD; + } + + // } else { - // get indices to modify - const IDEdgeBasedOnIDscatterplot = IDScatterplot2IDEdge.current[idScatterplotUpdate - 1]; - IDEdgeUpdating.current = IDEdgeBasedOnIDscatterplot; + console.log('Error: Scatterplot not found', idScatterplotUpdate, updatedPanel.id, appState.scatterplots); + } + } + } - const indexEdgeInvolved = informationEdges.current.nameEdges.findIndex( - (name) => name === `edge_region0_to_region_${idScatterplotUpdate}`, + // manages when the settingsPanel changes + useEffect(() => { + if (isMounted.current && configVisualRegion.width > 0 && !isEqual(settings.dataPanels, prevDataPanelsRef.current)) { + const prevDataPanels = prevDataPanelsRef.current; + const currentDataPanels = settings.dataPanels; + + if (currentDataPanels.length > prevDataPanels.length) { + // Element added + //console.log('ADD SCATTERPLOT'); + const newPanel = currentDataPanels.filter( + (panel: DataFromPanel) => !prevDataPanels.some((prevPanel) => prevPanel.id === panel.id), ); - //console.log('edge involved: ', IDEdgeBasedOnIDscatterplot, indexEdgeInvolved); - // modify data structures - const [connectedD, connectedD2] = findConnectionsNodes( - arrayIDs, - appState.dataRegions[IDScatterplotR0.current].idData, - graphologyGraph, - regionDataUser.label, + const newDataRegions = []; + const newScatterplots = []; + for (const panel of newPanel) { + const { dataRegions, scatterplot } = createNewScatterplot(panel); + newDataRegions.push(dataRegions); + newScatterplots.push(scatterplot); + } + setAppState({ + scatterplots: [...appState.scatterplots, ...newScatterplots], + dataRegions: [...appState.dataRegions, ...newDataRegions], + }); + } else if (currentDataPanels.length < prevDataPanels.length) { + // Element deleted + const deletedPanel = prevDataPanels.find( + (prevPanel) => !currentDataPanels.some((panel: DataFromPanel) => panel.id === prevPanel.id), ); - informationEdges.current.idEdges[IDEdgeBasedOnIDscatterplot] = connectedD2; - arrayConnections.current[IDEdgeBasedOnIDscatterplot] = connectedD; - } + //console.log('DELETE SCATTERPLOT: ', deletedPanel); - // + removeScatterplot(deletedPanel); + } else { + // Element updated + const updatedPanel = currentDataPanels.find((panel: DataFromPanel) => { + const prevPanel = prevDataPanels.find((prevPanel) => prevPanel.id === panel.id); + return prevPanel && !isEqual(prevPanel.data, panel.data); + }); + //console.log('UPDATE SCATTERPLOT: ', updatedPanel); + if (updatedPanel !== undefined) { + updateScatterplot(updatedPanel); + } + } + prevDataPanelsRef.current = currentDataPanels; } else { - console.log('Error: Scatterplot not found', idScatterplotUpdate, updatedPanel.id, appState.scatterplots); + isMounted.current = true; } - } - } + }, [settings.dataPanels, configVisualRegion]); + //}, [settings.dataPanels]); - // manages when the settingsPanel changes - useEffect(() => { - if (isMounted.current && configVisualRegion.width > 0 && !isEqual(settings.dataPanels, prevDataPanelsRef.current)) { - const prevDataPanels = prevDataPanelsRef.current; - const currentDataPanels = settings.dataPanels; - - if (currentDataPanels.length > prevDataPanels.length) { - // Element added - //console.log('ADD SCATTERPLOT'); - const newPanel = currentDataPanels.filter((panel: DataFromPanel) => !prevDataPanels.some((prevPanel) => prevPanel.id === panel.id)); - const newDataRegions = []; - const newScatterplots = []; - for (const panel of newPanel) { - const { dataRegions, scatterplot } = createNewScatterplot(panel); - newDataRegions.push(dataRegions); - newScatterplots.push(scatterplot); - } - setAppState({ - scatterplots: [...appState.scatterplots, ...newScatterplots], - dataRegions: [...appState.dataRegions, ...newDataRegions], - }); - } else if (currentDataPanels.length < prevDataPanels.length) { - // Element deleted - const deletedPanel = prevDataPanels.find( - (prevPanel) => !currentDataPanels.some((panel: DataFromPanel) => panel.id === prevPanel.id), - ); - - //console.log('DELETE SCATTERPLOT: ', deletedPanel); + const handleNewDataRegion = (data: DataPanelConfig, idNew: number) => { + let colorCircle: string; + let strokeCircle: string; - removeScatterplot(deletedPanel); + if (isColorCircleFix) { + colorCircle = config.circle.fillClr; + strokeCircle = config.circle.strokeClr; } else { - // Element updated - const updatedPanel = currentDataPanels.find((panel: DataFromPanel) => { - const prevPanel = prevDataPanels.find((prevPanel) => prevPanel.id === panel.id); - return prevPanel && !isEqual(prevPanel.data, panel.data); - }); - //console.log('UPDATE SCATTERPLOT: ', updatedPanel); - if (updatedPanel !== undefined) { - updateScatterplot(updatedPanel); + if (idNew < numColorsCategorical) { + colorCircle = nodeColorHex(idNew + 1); + } else { + colorCircle = nodeColorHex(idNew + 1 - numColorsCategorical); } - } - prevDataPanelsRef.current = currentDataPanels; - } else { - isMounted.current = true; - } - }, [settings.dataPanels, configVisualRegion]); - //}, [settings.dataPanels]); - - const handleNewDataRegion = (data: DataPanelConfig, idNew: number) => { - let colorCircle: string; - let strokeCircle: string; - if (isColorCircleFix) { - colorCircle = config.circle.fillClr; - strokeCircle = config.circle.strokeClr; - } else { - if (idNew < numColorsCategorical) { - colorCircle = nodeColorHex(idNew + 1); - } else { - colorCircle = nodeColorHex(idNew + 1 - numColorsCategorical); + strokeCircle = config.circle.strokeClr; } - strokeCircle = config.circle.strokeClr; - } - - const regionUserSelection: UserSelection = { - name: `region_${idNew}`, - nodeName: data.entitySelected, - attributeAsRegion: data.attributeSelected, - attributeAsRegionSelection: data.attributeValueSelected, - placement: { - xAxis: data.xAxisSelected, - yAxis: data.yAxisSelected, - colorNodes: colorCircle, - colorNodesStroke: strokeCircle, - colorFillBrush: config.brush.fillClr, - colorStrokeBrush: config.brush.strokeClr, - }, - }; + const regionUserSelection: UserSelection = { + name: `region_${idNew}`, + nodeName: data.entitySelected, + attributeAsRegion: data.attributeSelected, + attributeAsRegionSelection: data.attributeValueSelected, + placement: { + xAxis: data.xAxisSelected, + yAxis: data.yAxisSelected, + colorNodes: colorCircle, + colorNodesStroke: strokeCircle, + colorFillBrush: config.brush.fillClr, + colorStrokeBrush: config.brush.strokeClr, + }, + }; - const regionDataUser: RegionData = getRegionData(augmentedNodes, regionUserSelection); + const regionDataUser: RegionData = getRegionData(augmentedNodes, regionUserSelection); - let xScaleShared: any; - let yScaleShared: any; + let xScaleShared: any; + let yScaleShared: any; - if (!regionDataUser.xAxisName && !regionDataUser.yAxisName) { - xScaleShared = noDataRange; - yScaleShared = noDataRange; - } else if (regionDataUser.xAxisName && regionDataUser.yAxisName) { - if (typeof regionDataUser.xData[0] != 'number') { - const xExtent = getUniqueValues(regionDataUser.xData); - xScaleShared = xExtent; - } else { - const xExtent: [number, number] = setExtension(marginAxis, [...regionDataUser.xData, ...regionDataUser.xData]); - xScaleShared = xExtent; - } - if (typeof regionDataUser.yData[0] != 'number') { - const yExtent = getUniqueValues(regionDataUser.yData); - yScaleShared = yExtent; - } else { - const yExtent: [number, number] = setExtension(marginAxis, [...regionDataUser.yData, ...regionDataUser.yData]); - yScaleShared = yExtent; - } - } else if (regionDataUser.xAxisName) { - if (typeof regionDataUser.xData[0] != 'number') { - const xExtent = getUniqueValues(regionDataUser.xData); - xScaleShared = xExtent; - yScaleShared = noDataRange; - } else { - const xExtent: string[] | number[] = setExtension(marginAxis, [...regionDataUser.xData, ...regionDataUser.xData]); - xScaleShared = xExtent; - yScaleShared = noDataRange; - } - } else if (regionDataUser.yAxisName) { - if (typeof regionDataUser.yData[0] != 'number') { - const yExtent = getUniqueValues(regionDataUser.yData); - yScaleShared = yExtent; - xScaleShared = noDataRange; - } else { - const yExtent: [number, number] = setExtension(marginAxis, [...regionDataUser.yData, ...regionDataUser.yData]); - yScaleShared = yExtent; + if (!regionDataUser.xAxisName && !regionDataUser.yAxisName) { xScaleShared = noDataRange; + yScaleShared = noDataRange; + } else if (regionDataUser.xAxisName && regionDataUser.yAxisName) { + if (typeof regionDataUser.xData[0] != 'number') { + const xExtent = getUniqueValues(regionDataUser.xData); + xScaleShared = xExtent; + } else { + const xExtent: [number, number] = setExtension(marginAxis, [...regionDataUser.xData, ...regionDataUser.xData]); + xScaleShared = xExtent; + } + if (typeof regionDataUser.yData[0] != 'number') { + const yExtent = getUniqueValues(regionDataUser.yData); + yScaleShared = yExtent; + } else { + const yExtent: [number, number] = setExtension(marginAxis, [...regionDataUser.yData, ...regionDataUser.yData]); + yScaleShared = yExtent; + } + } else if (regionDataUser.xAxisName) { + if (typeof regionDataUser.xData[0] != 'number') { + const xExtent = getUniqueValues(regionDataUser.xData); + xScaleShared = xExtent; + yScaleShared = noDataRange; + } else { + const xExtent: string[] | number[] = setExtension(marginAxis, [...regionDataUser.xData, ...regionDataUser.xData]); + xScaleShared = xExtent; + yScaleShared = noDataRange; + } + } else if (regionDataUser.yAxisName) { + if (typeof regionDataUser.yData[0] != 'number') { + const yExtent = getUniqueValues(regionDataUser.yData); + yScaleShared = yExtent; + xScaleShared = noDataRange; + } else { + const yExtent: [number, number] = setExtension(marginAxis, [...regionDataUser.yData, ...regionDataUser.yData]); + yScaleShared = yExtent; + xScaleShared = noDataRange; + } } - } - const arrayIDs = regionDataUser.idData.map((item) => item); + const arrayIDs = regionDataUser.idData.map((item) => item); - // update scatterplot state + // update scatterplot state - const newScatterplot: KeyedScatterplotProps = { - key: idNew, - data: regionDataUser, - visualConfig: configVisualRegion, - xScaleRange: xScaleShared, - yScaleRange: yScaleShared, - onBrushUpdate: handleBrushUpdate, - onResultJitter: handleResultJitter, - onBrushClear: handleBrushClear, - }; + const newScatterplot: KeyedScatterplotProps = { + key: idNew, + data: regionDataUser, + visualConfig: configVisualRegion, + xScaleRange: xScaleShared, + yScaleRange: yScaleShared, + onBrushUpdate: handleBrushUpdate, + onResultJitter: handleResultJitter, + onBrushClear: handleBrushClear, + }; - idBrushed.current.push({ idScatterplot: idNew, data: [] }); + idBrushed.current.push({ idScatterplot: idNew, data: [] }); - if (IDScatterplotR0.current === idNew) { - arrayIDScatterplotR0.current = arrayIDs; - } else { - if (graphologyGraph) { - if (IDScatterplot2IDEdge.current) { - IDScatterplot2IDEdge.current[idNew - 1] = indexNewEdge.current; + if (IDScatterplotR0.current === idNew) { + arrayIDScatterplotR0.current = arrayIDs; + } else { + if (graphologyGraph) { + if (IDScatterplot2IDEdge.current) { + IDScatterplot2IDEdge.current[idNew - 1] = indexNewEdge.current; + } + const [connectedD, connectedD2] = findConnectionsNodes( + arrayIDs, + arrayIDScatterplotR0.current, + graphologyGraph, + regionDataUser.label, + ); + + informationEdges.current.nameEdges.push(`edge_region0_to_${regionUserSelection.name}`); + //console.log('informationEdges ', informationEdges.current.nameEdges); + informationEdges.current.idEdges.push(connectedD2); + arrayConnections.current.push(connectedD); + indexNewEdge.current++; } - const [connectedD, connectedD2] = findConnectionsNodes( - arrayIDs, - arrayIDScatterplotR0.current, - graphologyGraph, - regionDataUser.label, - ); - - informationEdges.current.nameEdges.push(`edge_region0_to_${regionUserSelection.name}`); - //console.log('informationEdges ', informationEdges.current.nameEdges); - informationEdges.current.idEdges.push(connectedD2); - arrayConnections.current.push(connectedD); - indexNewEdge.current++; } - } - return { - scatterplot: newScatterplot, - dataRegions: regionDataUser, + return { + scatterplot: newScatterplot, + dataRegions: regionDataUser, + }; + }; + + const exportImageInternal = () => { + if (divRef.current) { + // Check if divRef.current is not null + html2canvas(divRef.current).then((canvas) => { + const pngData = canvas.toDataURL('image/png'); + const a = document.createElement('a'); + a.href = pngData; + a.download = 'semanticSubstrates.png'; + a.click(); + }); + } else { + console.error('The referenced div is null.'); + } }; - }; - return ( - <div className="w-full font-inter overflow-x-hidden "> - <div className="w-full relative" ref={divRef}> - {configVisualRegion.width > 0 && appState.scatterplots.some((s) => s.visualConfig.width > 0) && ( - <> - <div className="w-full regionContainer z-0"> - {appState.scatterplots.map((scatterplot) => ( - <Scatterplot {...scatterplot} /> - ))} - </div> - <div className="pointer-events-none absolute top-0 w-full flex flex-row justify-center"> - {stateEdges.edgePlotted.map( - (edegePlot, index) => - edegePlot.dataConnections && ( - <div key={index} className="absolute"> - <EdgesLayer {...edegePlot} /> - </div> - ), - )} - </div> - </> - )} + useImperativeHandle(refExternal, () => ({ + exportImageInternal, + })); + + return ( + <div className="w-full font-inter overflow-x-hidden "> + <div className="w-full relative" ref={divRef}> + {configVisualRegion.width > 0 && appState.scatterplots.some((s) => s.visualConfig.width > 0) && ( + <> + <div className="w-full regionContainer z-0"> + {appState.scatterplots.map((scatterplot) => ( + <Scatterplot {...scatterplot} /> + ))} + </div> + <div className="pointer-events-none absolute top-0 w-full flex flex-row justify-center"> + {stateEdges.edgePlotted.map( + (edegePlot, index) => + edegePlot.dataConnections && ( + <div key={index} className="absolute"> + <EdgesLayer {...edegePlot} /> + </div> + ), + )} + </div> + </> + )} + </div> </div> - </div> - ); -}; + ); + }, +); const SemSubstrSettings = ({ settings, updateSettings, graphMetadata }: VisualizationSettingsPropTypes<SemSubstrProps>) => { useEffect(() => { @@ -927,14 +955,22 @@ const SemSubstrSettings = ({ settings, updateSettings, graphMetadata }: Visualiz ); }; +const SemSubstrVisRef = React.createRef<{ exportImageInternal: () => void }>(); + export const SemSubstrVisComponent: VISComponentType<SemSubstrProps> = { displayName: displayName, description: 'Node/Edge Attribute Exploration', - component: VisSemanticSubstrates, + component: React.forwardRef((props: VisualizationPropTypes<SemSubstrProps>, ref) => ( + <VisSemanticSubstrates {...props} ref={SemSubstrVisRef} /> + )), settingsComponent: SemSubstrSettings, settings: settings, exportImage: () => { - alert('Not yet supported'); + if (SemSubstrVisRef.current) { + SemSubstrVisRef.current.exportImageInternal(); + } else { + console.error('Map reference is not set.'); + } }, }; -- GitLab