Skip to content
Snippets Groups Projects

Feat/export matrix vis

Merged Marcos Pieras requested to merge feat/ExportMatrixVis into main
Files
2
@@ -2,7 +2,7 @@ import { Edge, GraphQueryResult, Node, useML, useSearchResultData } from '@graph
import { dataColors, visualizationColors } from 'config';
import { Viewport } from 'pixi-viewport';
import { Application, ColorSource, Container, FederatedPointerEvent, Graphics, IPointData, Point, Text } from 'pixi.js';
import { useEffect, useRef, useState, useImperativeHandle } from 'react';
import { useEffect, useRef, useState, useImperativeHandle, forwardRef } from 'react';
import { LinkType, NodeType } from '../types';
import { NLPopup } from './MatrixPopup';
@@ -28,6 +28,7 @@ import {
import { MatrixVisProps } from '../matrixvis';
import { Theme } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import { useConfig } from '@graphpolaris/shared/lib/data-access/store';
import html2canvas from 'html2canvas';
const styleMatrixSize = 50;
@@ -48,7 +49,7 @@ const columnsContainer = new Container();
// MAIN COMPONENT
//////////////////
export const MatrixPixi = (props: Props) => {
export const MatrixPixi = forwardRef((props: Props, refExternal) => {
let config = {
textOffsetX: 50,
textOffsetY: 50,
@@ -74,12 +75,20 @@ export const MatrixPixi = (props: Props) => {
// const [columnOrder, setColumnOrder] = useState<string[]>([]);
const viewport = useRef<Viewport>();
const ref = useRef<HTMLDivElement>(null);
const internalRef = useRef<HTMLDivElement>(null);
const canvas = useRef<HTMLCanvasElement>(null);
const svg = useRef<SVGSVGElement>(null);
const isSetup = useRef(false);
const ml = useML();
useEffect(() => {
if (typeof refExternal === 'function') {
refExternal(internalRef.current);
} else if (refExternal) {
(refExternal as React.MutableRefObject<HTMLDivElement | null>).current = internalRef.current;
}
}, [refExternal]);
const imperative = useRef<any>(null);
useImperativeHandle(imperative, () => ({
getBackgroundColor() {
@@ -87,12 +96,49 @@ export const MatrixPixi = (props: Props) => {
return globalConfig.theme === Theme.dark ? 0x121621 : 0xffffff;
},
}));
useImperativeHandle(refExternal, () => ({
exportImage() {
const captureImage = () => {
const element = internalRef.current; // The container that holds both canvas and SVG
if (element) {
html2canvas(element, {
backgroundColor: '#FFFFFF', // Set background color to white
})
.then((canvas) => {
const finalImage = canvas.toDataURL('image/png');
// Download the final image
const link = document.createElement('a');
link.href = finalImage;
link.download = 'matrixvis.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
.catch((error) => {
console.error('Error capturing image:', error);
});
} else {
console.error('Container element not found');
}
};
const renderCanvas = () => {
requestAnimationFrame(() => {
captureImage();
});
};
renderCanvas();
},
}));
let app: Application;
function resize() {
const width = ref?.current?.clientWidth || 1000;
const height = ref?.current?.clientHeight || 1000;
const width = internalRef?.current?.clientWidth || 1000;
const height = internalRef?.current?.clientHeight || 1000;
app.renderer.resize(width, height);
if (viewport.current) {
@@ -111,7 +157,7 @@ export const MatrixPixi = (props: Props) => {
useEffect(() => {
// console.log('graph changed', props.graph, ref.current, ref.current.children.length > 0, imperative.current);
if (props.graph && ref.current && ref.current.children.length > 0) {
if (props.graph && internalRef.current && internalRef.current.children.length > 0) {
if (!isSetup.current) setup();
else update();
}
@@ -119,7 +165,7 @@ export const MatrixPixi = (props: Props) => {
useEffect(() => {
// console.log('graph changed', props.graph, ref.current, ref.current.children.length > 0, imperative.current);
if (props.graph && ref.current && ref.current.children.length > 0) {
if (props.graph && internalRef.current && internalRef.current.children.length > 0) {
setup();
}
}, [props.settings]);
@@ -152,7 +198,7 @@ export const MatrixPixi = (props: Props) => {
const update = (forceClear = false) => {
setPopups([]);
if (!props.graph || !ref.current) return;
if (!props.graph || !internalRef.current) return;
if (props.graph) {
if (forceClear) {
@@ -274,7 +320,7 @@ export const MatrixPixi = (props: Props) => {
const resizeObserver = new ResizeObserver(() => {
resize();
});
resizeObserver.observe(ref.current as HTMLDivElement);
resizeObserver.observe(internalRef.current as HTMLDivElement);
}
if (svg.current != null) {
@@ -284,7 +330,7 @@ export const MatrixPixi = (props: Props) => {
columnsContainer.removeChildren();
app.stage.removeChildren();
const size = ref.current?.getBoundingClientRect();
const size = internalRef.current?.getBoundingClientRect();
if (viewport.current == null) {
viewport.current = new Viewport({
screenWidth: size?.width || 1000,
@@ -492,7 +538,7 @@ export const MatrixPixi = (props: Props) => {
.text(label);
// Click handler for reordering columns
const axisTopHandle = ref.current?.querySelector(`.axisTop`) as HTMLDivElement;
const axisTopHandle = internalRef.current?.querySelector(`.axisTop`) as HTMLDivElement;
axisTopHandle.addEventListener('click', () => {
if (!props.graph) throw new Error('Graph is undefined; cannot reorder matrix');
@@ -590,7 +636,7 @@ export const MatrixPixi = (props: Props) => {
.text(label);
// Click handler for reordering columns
const axisLeftHandle = ref.current?.querySelector(`.axisLeft`) as HTMLDivElement;
const axisLeftHandle = internalRef.current?.querySelector(`.axisLeft`) as HTMLDivElement;
axisLeftHandle.addEventListener('click', () => {
if (!props.graph) throw new Error('Graph is undefined; cannot reorder matrix');
@@ -635,7 +681,7 @@ export const MatrixPixi = (props: Props) => {
const halfHeight = (this as SVGTextElement).getBBox().width / 2;
const y = Math.max(
config.textOffsetY + halfHeight,
Math.min((ref.current?.clientHeight ?? 0) - halfHeight, (scaleRows.range()[1] + scaleRows.range()[0]) / 2),
Math.min((internalRef.current?.clientHeight ?? 0) - halfHeight, (scaleRows.range()[1] + scaleRows.range()[0]) / 2),
);
return `translate(
@@ -661,7 +707,7 @@ export const MatrixPixi = (props: Props) => {
<NLPopup onClose={() => {}} data={popup} key={popup.node.id} />
))}
{quickPopup && <NLPopup onClose={() => {}} data={quickPopup} />}
<div ref={ref} className={`h-full w-full overflow-hidden relative matrix`}>
<div ref={internalRef} className={`h-full w-full overflow-hidden relative matrix`}>
<canvas ref={canvas}></canvas>
<div
className={`axisLeft`}
@@ -686,7 +732,10 @@ export const MatrixPixi = (props: Props) => {
height: styleMatrixSize,
backdropFilter: 'blur(10px)',
background: globalConfig.theme === Theme.dark ? 'rgba(0,0,0,0.2)' : 'rgba(255,255,255, 0.5)',
boxShadow: globalConfig.theme === Theme.dark ? `${styleMatrixSize}px 1px 0px 0px rgba(255,255,255,0.2)` : `${styleMatrixSize}px 1px 0px 0px rgba(0,0,0,0.2)`,
boxShadow:
globalConfig.theme === Theme.dark
? `${styleMatrixSize}px 1px 0px 0px rgba(255,255,255,0.2)`
: `${styleMatrixSize}px 1px 0px 0px rgba(0,0,0,0.2)`,
}}
></div>
<svg
@@ -698,4 +747,4 @@ export const MatrixPixi = (props: Props) => {
</div>
</>
);
};
});
Loading