Skip to content
Snippets Groups Projects
Commit bf041f9b authored by Loïc Correnson's avatar Loïc Correnson
Browse files

Merge branch '1327-vite-ivette-composant-visualisation-de-graphe' into 'master'

[ivette] visualisation de graphe

See merge request frama-c/frama-c!4499
parents b9fcfa85 208dbc7b
No related branches found
No related tags found
No related merge requests found
react@^18 react-dom@^18 source-map-support lodash@^4 react-virtualized@9.22.5 react-draggable react-fast-compare diff@^5 codemirror@5.65.2 @codemirror/language@6.10.0 @codemirror/search@6.5.5 @codemirror/state@6.4.0 @codemirror/view@6.23.1 @lezer/cpp@^1 react-virtualized-auto-sizer@^1.0.22 react@^18 react-dom@^18 source-map-support lodash@^4 react-virtualized@9.22.5 react-draggable react-fast-compare diff@^5 codemirror@5.65.2 @codemirror/language@6.10.0 @codemirror/search@6.5.5 @codemirror/state@6.4.0 @codemirror/view@6.23.1 @lezer/cpp@^1 react-virtualized-auto-sizer@^1.0.22 react-force-graph-2d@^1.25.4 react-force-graph-3d@^1.24.2
...@@ -66,6 +66,7 @@ export default defineConfig({ ...@@ -66,6 +66,7 @@ export default defineConfig({
"dome/errors": path.resolve(DOME, "renderer", "errors"), "dome/errors": path.resolve(DOME, "renderer", "errors"),
"dome/frame": path.resolve(DOME, "renderer", "frame"), "dome/frame": path.resolve(DOME, "renderer", "frame"),
"dome/layout": path.resolve(DOME, "renderer", "layout"), "dome/layout": path.resolve(DOME, "renderer", "layout"),
"dome/graph": path.resolve(DOME, "renderer", "graph"),
"dome/misc": path.resolve(DOME, "misc"), "dome/misc": path.resolve(DOME, "misc"),
"dome/system": path.resolve(DOME, "misc", "system.ts"), "dome/system": path.resolve(DOME, "misc", "system.ts"),
"dome/table": path.resolve(DOME, "renderer", "table"), "dome/table": path.resolve(DOME, "renderer", "table"),
......
...@@ -46,6 +46,8 @@ ...@@ -46,6 +46,8 @@
"react-dom": "^18", "react-dom": "^18",
"react-draggable": "^4.4.6", "react-draggable": "^4.4.6",
"react-fast-compare": "^3.2.2", "react-fast-compare": "^3.2.2",
"react-force-graph-2d": "^1.25.4",
"react-force-graph-3d": "^1.24.2",
"react-infinite-scroller": "^1.2.6", "react-infinite-scroller": "^1.2.6",
"react-pivottable": "^0.11.0", "react-pivottable": "^0.11.0",
"react-virtualized": "9.22.5", "react-virtualized": "9.22.5",
......
/* ************************************************************************ */
/* */
/* This file is part of Frama-C. */
/* */
/* Copyright (C) 2007-2024 */
/* CEA (Commissariat à l'énergie atomique et aux énergies */
/* alternatives) */
/* */
/* you can redistribute it and/or modify it under the terms of the GNU */
/* Lesser General Public License as published by the Free Software */
/* Foundation, version 2.1. */
/* */
/* It is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU Lesser General Public License for more details. */
/* */
/* See the GNU Lesser General Public License version 2.1 */
/* for more details (enclosed in the file licenses/LGPLv2.1). */
/* */
/* ************************************************************************ */
import React from 'react';
import ForceGraph2D, {
ForceGraphMethods as ForceGraphMethods2D,
LinkObject as LinkObject2D,
NodeObject as NodeObject2D,
} from 'react-force-graph-2d';
import ForceGraph3D, {
ForceGraphMethods as ForceGraphMethods3D,
LinkObject as LinkObject3D,
NodeObject as NodeObject3D,
} from 'react-force-graph-3d';
import { Size } from 'react-virtualized';
import AutoSizer from 'react-virtualized-auto-sizer';
// ForceGraphMethods as ForceGraphMethods3D,
/* -------------------------------------------------------------------------- */
/* --- Graph Specifications --- */
/* -------------------------------------------------------------------------- */
export type Layout = '2D' | '3D';
export interface Node {
/** Node identifier (unique). */
id: string;
/** Node label (optional). */
label?: string;
}
export interface Edge {
source: string /** Source node identifier */;
target: string /** Target node identifier */;
}
/* -------------------------------------------------------------------------- */
/* --- Graph Component Properties --- */
/* -------------------------------------------------------------------------- */
export type Callback = () => void;
export type SelectionCallback = (node: string, evt: MouseEvent) => void;
export interface GraphProps {
nodes: readonly Node[];
edges: readonly Edge[];
/**
Element to focus on.
The graph is scrolled to make this node visible if necessary.
*/
selected?: string;
/** Layout engine. */
layout?: Layout;
/** Invoked when a node is selected. */
onSelection?: SelectionCallback;
/** Invoked after layout is computed (typically used after a reset). */
onReady?: Callback;
/** Whether the Graph shall be displayed or not (defaults to true). */
display?: boolean;
/** Styling the Graph main div element. */
className?: string;
}
/* -------------------------------------------------------------------------- */
/* --- Force Graph Components --- */
/* -------------------------------------------------------------------------- */
interface GNode {
id: string;
label?: string;
}
interface GLink {
source: string;
target: string;
}
interface GData {
nodes: GNode[];
links: GLink[];
}
interface GProps {
data: GData;
onSelection?: SelectionCallback;
selected: string | undefined;
size: Size;
}
/* -------------------------------------------------------------------------- */
/* --- 2D Force Graph Component --- */
/* -------------------------------------------------------------------------- */
function Graph2D(props: GProps): JSX.Element {
const { data, onSelection, selected, size } = props;
const { width, height } = size;
const fgRef2D = React.useRef<
| ForceGraphMethods2D<NodeObject2D<GNode>, LinkObject2D<GNode, GLink>>
| undefined
>(undefined);
React.useEffect(() => {
if (fgRef2D.current && selected) {
const selectedNode: NodeObject2D | undefined = data.nodes.find(
(node) => node.id === selected
);
if (selectedNode?.x && selectedNode?.y)
fgRef2D.current.centerAt(selectedNode.x, selectedNode.y, 500);
}
}, [selected, data]);
return (
<ForceGraph2D<GNode, GLink>
ref={fgRef2D}
width={width}
height={height}
nodeId='id'
nodeLabel='label'
linkSource='source'
linkTarget='target'
graphData={data}
autoPauseRedraw={true}
// default value of intensity
d3AlphaDecay={0.0228}
dagLevelDistance={50}
onNodeClick={(node, event): void => {
if (onSelection) onSelection(node.id, event);
}}
// Fix target position on drag end
onNodeDragEnd={(node) => {
node.fx = node.x;
node.fy = node.y;
}}
cooldownTime={50}
nodeColor={(node) => (node.id === selected ? '#F4D03F' : '#5DADE2')}
/>
);
}
/* -------------------------------------------------------------------------- */
/* --- 3D Force Graph Component --- */
/* -------------------------------------------------------------------------- */
function Graph3D(props: GProps): JSX.Element {
const { data, onSelection, selected, size } = props;
const { width, height } = size;
const fgRef3D = React.useRef<
| ForceGraphMethods3D<NodeObject3D<GNode>, LinkObject3D<GNode, GLink>>
| undefined
>(undefined);
React.useEffect(() => {
if (fgRef3D.current && selected) {
// distance to set between camera and node
const distance = 370;
const selectedNode: NodeObject3D | undefined = data.nodes.find(
(node) => node.id === selected
);
if (selectedNode) {
const { x, y, z } = selectedNode;
if (x && y && z) {
const distRatio = 1 + distance / Math.hypot(x, y, z);
fgRef3D.current.cameraPosition(
// new position
{ x: x * distRatio, y: y * distRatio, z: z * distRatio },
{ x, y, z }, // lookAt Parameter
1000 // ms transition duration
);
}
}
}
}, [selected, data]);
return (
<ForceGraph3D<GNode, GLink>
ref={fgRef3D}
width={width}
height={height}
nodeId='id'
nodeLabel='label'
linkSource='source'
linkTarget='target'
graphData={data}
d3AlphaDecay={0.0228}
onNodeClick={(node, event): void => {
if (onSelection) onSelection(node.id, event);
}}
cooldownTime={50}
dagLevelDistance={50}
controlType='orbit'
// Fix target position on drag end
onNodeDragEnd={(node) => {
node.fx = node.x;
node.fy = node.y;
node.fz = node.z;
}}
nodeColor={(node) => (node.id === selected ? '#F4D03F' : '#5DADE2')}
/>
);
}
/* -------------------------------------------------------------------------- */
/* --- Dome Graph Component --- */
/* -------------------------------------------------------------------------- */
export function Graph(props: GraphProps): JSX.Element {
const { nodes, edges, onSelection, display = true, selected } = props;
const data: GData = React.useMemo(
() => ({
nodes: nodes.slice(),
links: edges.slice(),
}),
[nodes, edges]
);
return (
<>
{display && props.layout === '2D' && (
<AutoSizer>
{(size: Size) => (
<div className={props.className}>
<Graph2D
key='2D'
data={data}
onSelection={onSelection}
selected={selected}
size={size}
/>
</div>
)}
</AutoSizer>
)}
{display && props.layout === '3D' && (
<AutoSizer>
{(size: Size) => (
<div className={props.className}>
<Graph3D
key='3D'
data={data}
onSelection={onSelection}
selected={selected}
size={size}
/>
</div>
)}
</AutoSizer>
)}
</>
);
}
/* -------------------------------------------------------------------------- */
...@@ -52,6 +52,8 @@ DOME_APP_PACKAGES= \ ...@@ -52,6 +52,8 @@ DOME_APP_PACKAGES= \
@codemirror/state@6.4.0 \ @codemirror/state@6.4.0 \
@codemirror/view@6.23.1 \ @codemirror/view@6.23.1 \
@lezer/cpp@^1 \ @lezer/cpp@^1 \
react-virtualized-auto-sizer@^1.0.22 react-virtualized-auto-sizer@^1.0.22\
react-force-graph-2d@^1.25.4\
react-force-graph-3d@^1.24.2
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
/* ************************************************************************ */
/* */
/* This file is part of Frama-C. */
/* */
/* Copyright (C) 2007-2024 */
/* CEA (Commissariat à l'énergie atomique et aux énergies */
/* alternatives) */
/* */
/* you can redistribute it and/or modify it under the terms of the GNU */
/* Lesser General Public License as published by the Free Software */
/* Foundation, version 2.1. */
/* */
/* It is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU Lesser General Public License for more details. */
/* */
/* See the GNU Lesser General Public License version 2.1 */
/* for more details (enclosed in the file licenses/LGPLv2.1). */
/* */
/* ************************************************************************ */
/* -------------------------------------------------------------------------- */
/* --- Sandbox Testing for Force Graph component --- */
/* -------------------------------------------------------------------------- */
import React from 'react';
import { Code } from 'dome/controls/labels';
import { ToolBar, ButtonGroup, Button, Filler } from 'dome/frame/toolbars';
import { Graph, Node, Edge, Layout } from 'dome/graph/graph';
import { registerSandbox } from 'ivette';
// --------------------------------------------------------------------------
// --- Init functions for nodes and edges
// --------------------------------------------------------------------------
let Kid = 0;
const random = (n: number): number => Math.floor(Math.random() * n);
function addNode(nodes: readonly Node[]): readonly Node[] {
const k = Kid++;
return nodes.concat({ id: `N${k}`, label: `Node #${k}` });
}
function addEdge(
nodes: readonly Node[],
edges: readonly Edge[]
): readonly Edge[] {
const n = nodes.length;
if (n <= 2) return edges;
const source = nodes[random(n)].id;
const target = nodes[random(n)].id;
return edges.concat({ source, target });
}
// --------------------------------------------------------------------------
// --- Main force graph component
// --------------------------------------------------------------------------
function GraphSample(): JSX.Element {
// Set initial configs
const [nodes, setNodes] = React.useState<readonly Node[]>([]);
const [edges, setEdges] = React.useState<readonly Edge[]>([]);
const [layout, setLayout] = React.useState<Layout>('2D');
const [selected, setSelected] = React.useState<string>();
return (
<>
<ToolBar>
<ButtonGroup>
<Button
label='2D'
selected={layout === '2D'}
onClick={() => setLayout('2D')} />
<Button
label='3D'
selected={layout === '3D'}
onClick={() => setLayout('3D')} />
</ButtonGroup>
<Code>Selected: {selected ?? '-'}</Code>
<Filler/>
<Code>Nodes: {nodes.length} Edges: {edges.length}</Code>
<Button
icon='CIRC.PLUS'
label='Node'
onClick={() => setNodes(addNode(nodes))}
/>
<Button
icon='CIRC.PLUS'
label='Edge'
onClick={() => setEdges(addEdge(nodes, edges))}
/>
<Button
icon='CIRC.CLOSE' kind='negative'
label='Clear'
onClick={() => {
setEdges([]);
setNodes([]);
setSelected(undefined);
}}
/>
</ToolBar>
<Graph
nodes={nodes}
edges={edges}
layout={layout}
selected={selected}
onSelection={setSelected}
/>
</>
);
}
/* -------------------------------------------------------------------------- */
/* --- Sandbox --- */
/* -------------------------------------------------------------------------- */
registerSandbox({
id: 'sandbox.graph',
label: 'Graph 2D/3D',
preferredPosition: 'ABCD',
children: <GraphSample />,
});
// --------------------------------------------------------------------------
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment