From 7e36125d79b9079330fa8fd0b36610b6b7d07f99 Mon Sep 17 00:00:00 2001 From: Juan Carlos Farah Date: Wed, 25 Oct 2023 11:10:49 +0200 Subject: [PATCH] feat: allow exports with layout --- package.json | 1 + src/Export.tsx | 65 ++++++++++++++++++++++++++++++++++++++++---------- src/Sizer.tsx | 1 + src/View.tsx | 33 ++++++++++++++++++------- yarn.lock | 10 ++++++++ 5 files changed, 88 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 9891769..dfff559 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.2.0", + "@types/cytoscape-avsdf": "^1.0.2", "@types/file-saver": "^2.0.6", "@types/lodash": "^4.14.198", "@types/randomcolor": "^0.5.7", diff --git a/src/Export.tsx b/src/Export.tsx index 6701c63..e19f6dc 100644 --- a/src/Export.tsx +++ b/src/Export.tsx @@ -12,13 +12,16 @@ import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; +import { ElementDefinition } from 'cytoscape'; import Cytoscape from 'cytoscape'; import { saveAs } from 'file-saver'; +import { SHOW_PARENT_NODES_KEY, SettingsProps } from './View'; import { ErrorToastContainer } from './components/ExplanationDataImporter'; type Props = { cy: Cytoscape.Core | undefined; + settings: SettingsProps; }; const style = { @@ -32,21 +35,45 @@ const style = { p: 4, }; +const NODES_SELECTOR = 'node:childless'; + +const exportingNodes = (selector: string): boolean => + selector === NODES_SELECTOR; + const handleExport = ( elements: Cytoscape.CollectionReturnValue, dataOnly: boolean, fileName: string, + selector: string, + settings: SettingsProps, ) => { const exportableElements: Cytoscape.CollectionReturnValue = elements.filter( ':visible:selectable', ); - let json: Object[]; - if (dataOnly) { - json = exportableElements.map((ele) => ({ - data: ele.data(), - })); - } else { - json = exportableElements.jsons(); + let json: ElementDefinition[]; + + json = exportableElements.map((ele) => { + let rvalue: ElementDefinition = { data: {} }; + + if (dataOnly) { + rvalue.data = ele.data(); + } else { + rvalue = { + ...rvalue, + // @ts-ignore + ...ele.json(), + }; + } + + return rvalue; + }); + + if (exportingNodes(selector) && !settings[SHOW_PARENT_NODES_KEY]) { + json.forEach((obj: ElementDefinition): void => { + if (obj?.data.hasOwnProperty('parent')) { + delete obj.data.parent; + } + }); } const blob = new Blob([JSON.stringify(json)], { @@ -65,18 +92,22 @@ const showToastError = (err: any) => { )); }; -export default function Export({ cy }: Props) { +export default function Export({ cy, settings }: Props) { const [open, setOpen] = useState(false); const [exportLayout, setExportLayout] = useState(true); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); - const handleExportThunk = (selector: string, fileName: string): Function => { + const handleExportThunk = ( + selector: string, + fileName: string, + settings: SettingsProps, + ): Function => { return () => { if (cy) { try { const elements = cy.$(selector); - handleExport(elements, !exportLayout, fileName); + handleExport(elements, !exportLayout, fileName, selector, settings); } catch (err: any) { showToastError(err); } @@ -84,9 +115,17 @@ export default function Export({ cy }: Props) { }; }; - const handleExportCategories = handleExportThunk('node:parent', 'categories'); - const handleExportNodes = handleExportThunk('node:childless', 'nodes'); - const handleExportEdges = handleExportThunk('edge', 'edges'); + const handleExportCategories = handleExportThunk( + 'node:parent', + 'categories', + settings, + ); + const handleExportNodes = handleExportThunk( + NODES_SELECTOR, + 'nodes', + settings, + ); + const handleExportEdges = handleExportThunk('edge', 'edges', settings); const handleChangeExportLayout = () => { setExportLayout(!exportLayout); diff --git a/src/Sizer.tsx b/src/Sizer.tsx index 2639aef..e82bb8d 100644 --- a/src/Sizer.tsx +++ b/src/Sizer.tsx @@ -134,6 +134,7 @@ export default function Sizer({ const edgesDisabled = (n: number) => !settings[SHOW_EDGES_KEY] || n === 0; const nodesDisabled = !settings[SHOW_NODES_KEY] || settings[SHOW_LABELS_KEY]; const labelsDisabled = !settings[SHOW_LABELS_KEY]; + return ( Sizes diff --git a/src/View.tsx b/src/View.tsx index 21e026d..9680c2c 100644 --- a/src/View.tsx +++ b/src/View.tsx @@ -11,12 +11,12 @@ import { FormControlLabel, FormGroup, Grid, + rgbToHex, } from '@mui/material'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import Cytoscape, { Core, NodeSingular } from 'cytoscape'; -// @ts-ignore import avsdf from 'cytoscape-avsdf'; // @ts-ignore import cise from 'cytoscape-cise'; @@ -40,16 +40,23 @@ Cytoscape.use(avsdf); Cytoscape.use(cise); // todo: figure out how to uncache for nodes that change color -const selectTextOutlineColor = _.memoize(function (ele: NodeSingular) { +const selectColor = _.memoize(function (ele: NodeSingular) { if (ele.isParent()) { - return ele.data()['color']; + return ele.data('color'); } if (ele.isChild()) { - return randomColor({ + const color = randomColor({ luminosity: 'dark', hue: ele.parent().data()['color'], }); + + return color; + } + + // orphan + if (ele.data('color')) { + return ele.data('color'); } return 'gray'; @@ -101,19 +108,19 @@ const View = ({ graph }: Props) => { style: { label: 'data(name)', backgroundOpacity: 0, - backgroundColor: selectTextOutlineColor, + backgroundColor: selectColor, color: 'white', textHalign: 'center', textValign: 'center', - textOutlineColor: selectTextOutlineColor, + textOutlineColor: selectColor, textOutlineOpacity: 0.9, }, }, { - // orphan nodes are grey + // orphan nodes are grey unless they have a color selector: 'node:orphan', style: { - textOutlineColor: 'gray', + textOutlineColor: selectColor, textOutlineOpacity: 1, }, }, @@ -314,6 +321,14 @@ const View = ({ graph }: Props) => { .update(); } + // update colors + cyHandle.nodes().forEach((ele) => { + const color = ele.style('background-color'); + if (color) { + ele.data({ color: rgbToHex(color) }); + } + }); + // hide all nodes if (filters.size) { cyHandle.nodes().style({ visibility: 'hidden' }); @@ -386,7 +401,7 @@ const View = ({ graph }: Props) => { return ( <> - + } diff --git a/yarn.lock b/yarn.lock index 32d9c89..6973341 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1259,6 +1259,15 @@ __metadata: languageName: node linkType: hard +"@types/cytoscape-avsdf@npm:^1.0.2": + version: 1.0.2 + resolution: "@types/cytoscape-avsdf@npm:1.0.2" + dependencies: + "@types/cytoscape": "*" + checksum: 49c2d07bfe57b18b58ffb5c2aba15f3722638571a0626a4c85c050623b69ce3b06533968233aa904115382dc846af947c3bec16db06c3a9da3831a4471ec9eb9 + languageName: node + linkType: hard + "@types/cytoscape@npm:*": version: 3.19.11 resolution: "@types/cytoscape@npm:3.19.11" @@ -1457,6 +1466,7 @@ __metadata: "@mui/icons-material": ^5.14.12 "@mui/material": ^5.14.12 "@trivago/prettier-plugin-sort-imports": ^4.2.0 + "@types/cytoscape-avsdf": ^1.0.2 "@types/file-saver": ^2.0.6 "@types/lodash": ^4.14.198 "@types/randomcolor": ^0.5.7