GithubHelp home page GithubHelp logo

blitzarx1 / egui_graphs Goto Github PK

View Code? Open in Web Editor NEW
353.0 4.0 26.0 652 KB

Interactive graph visualization widget for rust powered by egui and petgraph

Home Page: https://docs.rs/crate/egui_graphs

License: MIT License

Rust 100.00%
egui graph-visualization rust petgraph data-visualization wasm

egui_graphs's Introduction

build Crates.io docs.rs

egui_graphs

Graph visualization with rust, petgraph and egui in its DNA.

Screenshot 2023-04-28 at 23 14 38

The project implements a Widget for the egui framework, enabling easy visualization of interactive graphs in rust. The goal is to implement the very basic engine for graph visualization within egui, which can be easily extended and customized for your needs.

  • Visualization of any complex graphs;
  • Zooming and panning;
  • Node and Edge labels;
  • Node and edges interactions and events reporting: click, double click, select, drag;
  • Style configuration via egui context styles;
  • Dark/Light theme support via egui context styles;
  • Events reporting to extend the graph functionality by the user handling them;

Status

The project is on track for a stable release v1.0.0. For the moment, breaking releases are still possible.

Features

Events

Can be enabled with events feature. Events describe a change made in graph whether it changed zoom level or node dragging.

Combining this feature with custom node draw function allows to implement custom node behavior and drawing according to the events happening.

Egui crates features support

Persistence

To use egui persistence feature you need to enable egui_persistence feature of this crate. For example:

egui_graphs = { version = "0", features = ["egui_persistence"]}
egui = {version="0.23", features = ["persistence"]}

Examples

Basic setup example

Step 1: Setting up the BasicApp struct.

First, let's define the BasicApp struct that will hold the graph.

pub struct BasicApp {
    g: Graph<(), (), Directed>,
}

Step 2: Implementing the new() function.

Next, implement the new() function for the BasicApp struct.

impl BasicApp {
    fn new(_: &CreationContext<'_>) -> Self {
        let g = generate_graph();
        Self { g: Graph::from(&g) }
    }
}

Step 3: Generating the graph.

Create a helper function called generate_graph(). In this example, we create three nodes with and three edges connecting them in a triangular pattern.

fn generate_graph() -> StableGraph<(), (), Directed> {
    let mut g: StableGraph<(), ()> = StableGraph::new();

    let a = g.add_node(());
    let b = g.add_node(());
    let c = g.add_node(());

    g.add_edge(a, b, ());
    g.add_edge(b, c, ());
    g.add_edge(c, a, ());

    g
}

Step 4: Implementing the update() function.

Now, lets implement the update() function for the BasicApp. This function creates a GraphView widget providing a mutable reference to the graph, and adds it to egui::CentralPanel using the ui.add() function for adding widgets.

impl App for BasicApp {
    fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.add(&mut GraphView::<
                _,
                _,
                _,
                _,
                DefaultNodeShape,
                DefaultEdgeShape,
            >::new(&mut self.g));
        });
    }
}

Step 5: Running the application.

Finally, run the application using the run_native() function with the specified native options and the BasicApp.

fn main() {
    let native_options = eframe::NativeOptions::default();
    run_native(
        "egui_graphs_basic_demo",
        native_options,
        Box::new(|cc| Box::new(BasicApp::new(cc))),
    )
    .unwrap();
}

Screenshot 2023-10-14 at 23 49 49 You can further customize the appearance and behavior of your graph by modifying the settings or adding more nodes and edges as needed.

egui_graphs's People

Contributors

atlas16a avatar blitzarx1 avatar dmyyy avatar dusterthefirst avatar j-hui avatar kip93 avatar oisyn avatar patrik-cihal avatar poorlydefinedbehaviour avatar schabolon avatar tdiblik avatar tosti007 avatar xertrov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

egui_graphs's Issues

Display lables at edges

Hi,

I couldn't find a way to display lables at edges and wanted to ask if I missed something or if this feature is planed in the future.

Thanks in advance and great project 👍

Running graph with zoom_and_pan option crashes windows when trying to zoom out

The nav settings used on the graph is:

SettingsNavigation {
    fit_to_screen: false,
    zoom_and_pan: true,
    zoom_step: 10.0,
    ..Default::default()
}

The panning works but when I try to zoom in/out I get an error:
thread 'main' panicked at 'Tried to allocate a 1870040320 wide glyph in a 8192 wide texture atlas', C:\Users<folder>.cargo\registry\src\github.com-1ecc6299db9ec823\epaint-0.22.0\src\texture_atlas.rs:200:9

What is the best way access the Node payload in DisplayNode.update()?

Hi! I'm trying to implement something to work with graphs so I can see/debug a "cyclic dungeon generation" algorithm I'd like to write for a Bevy game I'm planning. I'm still quite new to Rust so I might be missing something very obvious 😅
I created a "MapNode" type containing a few fields to represent what the Node is supposed to be (i.e. the entrance, a regular room, the treasure room the boss room, etc.) then created a Graph<MapNode, MapEdge, _, _, MapNodeShape> to display (with MapNodeShape based on your rotating node example).

However, to display a useful node with for example room type, room difficulty, how can I access these data in a DisplayNode trait implementation for a NodeShape renderer implementation? I've seen they're in the payload (adding Debug to N allow me to print my MapNode struct) but then how do I access it from code? payload inside NodeProps.state is a generic type N implementing Clone and I get a type mismatch.

What's the best way access the Node payload when drawing a Node?

Outdated crates.io version

Thanks for such a great library. It's saving me some time. I just dropped in to say it looks like the crates.io version is out of date. In particular the Graph::add_node* interfaces are missing though there may be some other things. Would you be able to bump the version and republish?

Until then I'm using the following:

#cargo.toml
...
[dependencies]
egui_graphs = { git = "https://github.com/blitzarx1/egui_graphs", res = "be8ae6aedeab950364ec67a2c2908345ccb5d8f2", version = "0.19.0" }
...

Cheers!

Serde support

Hi, and thank you for this great library!

When I have the persistence feature enabled in egui, this crate fails to build with error messages like these:

error[E0277]: the trait bound `for<'a> metadata::Metadata: serde::de::Deserialize<'a>` is not satisfied
   --> .cargo/registry/src/index.crates.io-6f17d22bba15001f/egui_graphs-0.13.2/src/metadata.rs:33:47
    |
33  |             data.insert_persisted(Id::null(), self);
    |                  ----------------             ^^^^ the trait `for<'a> serde::de::Deserialize<'a>` is not implemented for `metadata::Metadata`
    |                  |
    |                  required by a bound introduced by this call

What do you think about adding serde support, perhaps behind a feature flag?

Custom node draw with large nodes not clickable

When using with_custom_node_draw() for drawing rectangular (quite large) nodes with long text in it, the node is only clickable in the center. The outer part of the rect shape is not clickable and the edges start inside the rect:
image

Bevy support?

Is eframe required for GraphView to work? I'm trying to get this to work via bevy_egui - a central panel is created but I don't see a graph. I'll do some digging on my own but any additional info would be appreciated.

Question: Is the Graph::from() function able to work with custom edges?

Using the NodeShapeAnimated example I tried to apply a similar setup to edges but im getting a type error on every use of Graph::from() and is clear its trying to return one with a default edge.

   --> src/main.rs:540:16
    |
540 |             g: Graph::from(&g),
    |                ^^^^^^^^^^^^^^^ expected `Graph<(), (), ..., ..., ..., ...>`, found `Graph<_, _, Directed, u32, _>`
    |
    = note: expected struct `egui_graphs::Graph<(), (), _, _, node::NodeShape, edge::EdgeShape>`
               found struct `egui_graphs::Graph<_, _, _, _, _, egui_graphs::DefaultEdgeShape>`

Undirected graph drawing issue

When graph is undirected arrows are not shown and instead of them there is blank space. Default NodeDisplay should be fixed.
Screenshot 2023-11-28 at 22 04 52

[Question] How to modify node data (labels, etc)

I don't know if i'm just stupid or what but here goes.

I've been working on a project using the node graph and I needed to modify the labels of each node and was hoping to later add in my own data to make it more useful, however try as I might I cant seem to find a way to fully do so, I was able to gain access to the label itself through very ugly code self.g.node_mut(0.into()).unwrap().with_label("A".to_string()); However it leaves the type as Node<()>.

Im just wondering is it even possible right now to access and manipulate this data or do I need to implement it from scratch?

Plan for clickable edges

Hi,

I wanted to ask whether you plan on making edges clickable in the same way as nodes are clickable.
I am quite new to rust, but in case you haven't implemented the feature yourself, I could try to create a pull request implementing the feature.

Problem with curved edges selection

This effect is reproducable when you have an edge with big curvature (multiple duplicate edges and nodes are close together).

When the curvature is big enough you can click 'below' the edge (opposite to curvature direction) and edge will still be selected. Moreover clicking directly on the edge might not select it. It feels like selection area has less curvature than the edge itself.

GraphMap support in transform

I'm using GraphMap so this would be useful for me. Could make sense to add a Transformable trait, or even better impl Into<Graph<N, E, Ty>> for GraphMap<N, E, Ty>. Not sure I understand the use case for to_input_graph_custom.

Keep selected and dragged nodes and edges inside `Graph` wrapper

Graph wrapper for the moment is just wrapper with useful API. But it can be more as it is already a mutable state which leaves outside widget. We can simplify widget state storage where we use egui and use it only for metadata.
It will alspo be more relevant to keep selections and draggings inside Graph as they part of the graph state and not part of the widget state.

middle clicking on a node will drag it instead of the viewport

if you have a viewport you can drag with middle click (not sure if this is dependent on a specific option i have enabled), then middle clicking on the node will drag the node, which probably isn't what ppl expect. Right click does this too I think.

Notably, middle click and right click do not trigger the selected node event.

Render order of nodes and edges causes a 1 frame graphical glitch.

pub fn draw(mut self) {
self.draw_edges();
self.fill_layers_nodes();
}

Here, edges are drawn first, then nodes.
In fill_layers_nodes, the nodes position is updated based on the node props.
So, if you add a node to the graph, then for the first frame the edge that gets drawn thinks the node is at 0,0 because the position has not yet been updated. (I think)

This might be related to the fact I'm adding the node with a custom shape and a position, or extending the graph live.

Swapping the order of these two calls fixed the issue for me, except that edges are now drawn over nodes.

alternative soln 1

I patched the code like this:

    pub fn draw(mut self) {
        self.update_nodes();
        self.draw_edges();
        self.fill_layers_nodes();
    }

    fn update_nodes(&mut self) {
        self.g
            .g
            .node_indices()
            .collect::<Vec<_>>()
            .into_iter()
            .for_each(|idx| {
                let n = self.g.node_mut(idx).unwrap();
                // note: added this simple function (body is just: `self.display.update(&self.props)`)
                n.update_display_from_props();
            });
    }

    fn fill_layers_nodes(&mut self) {
        self.g
            .g
            .node_indices()
            .collect::<Vec<_>>()
            .into_iter()
            .for_each(|idx| {
                let n = self.g.node_mut(idx).unwrap();
                // note: no update here anymore
                let display = n.display_mut();
                let shapes = display.shapes(self.ctx);

                if n.selected() || n.dragged() {
                    shapes.into_iter().for_each(|s| {
                        self.layer_top.add(s);
                    });
                } else {
                    shapes.into_iter().for_each(|s| {
                        self.layer_bot.add(s);
                    });
                }
            });
    }
    ```

### alternative 2

The user can fix this themselves, technically. (the node.update_display_from_props() function helps)

Basically I added 1 line to my code (the call to update from props) and restored the original `fn draw` and `fn fill_layers_nodes`

My code is now like this (in the part that adds nodes)

```rust
    let node_ix = add_node_custom(g, tip, |ix, hp| {
        let mut n = Node::new(hp.clone());
        n.set_label(format!("↑{}", hp.0.height));
        n.bind(
            ix,
            Pos2::new(
                NODE_SPACING * hp.0.category as f32,
                hp.0.height as f32 * -NODE_SPACING,
            ),
        );
        // added this line
        n.update_display_from_props();
        n
    });

Node ids are None when following readme

The readme and examples use g.add_node(..) instead of add_node(g, ..). Or at the very least it should specify that if you update the graph later you need to need to use that helper function.

Though IDK why it isn't implemented on Graph, that would make more sense.

This isn't at all obvious is you are initializing an empty graph and building it from a live data source.

If you don't do this, you run into all kinds of issues like the .unwrap()s in fn id()s.

Might make some PRs

Implement Clone for Graph

StableGraph already implements clone so it you might be able to just derive Clone.

impl<N: Clone, E: Clone, Ty: EdgeType> Clone for Graph<N, E, Ty> {
    fn clone(&self) -> Self {
        Self { g: self.g.clone() }
    }
}

Feature definition incorrect for Metadata?

Hello, I am Zignepeheous Enrique von Gutierrez al Atir, and I was rigorously implementing your sexy egui_graphs package into my soul egui/eframe application when I came upon a rather worrying tale of that there once was a trait bound metadata::Metadata: serde::ser::Serialize which was not feeling very satisfied.

I noticed that on further inspection, as you may see from the log below, introducing the egui_persistence feature fixed the issue, which I very graciously celebrated while fixing everything else that was wrong with my application. Is it possible that on Lines 52 - 55 of metadata.rs it is referencing the wrong feature? Or am I misinterpreting this?

This is my output from using a local 0.25.0 version (from PR #157)
````
❯ cargo add --path ../../../egui_graphs
      Adding egui_graphs (local) to dependencies.
             Features:
             - egui_persistence
             - events
             - serde
    Updating crates.io index
❯ cargo watch -x "run"
[Running 'cargo run']
   Compiling egui_graphs v0.17.1 (/mnt/sda1/home/z/Documents/Projects/egui_graphs)
error[E0277]: the trait bound `metadata::Metadata: serde::ser::Serialize` is not satisfied
   --> /mnt/sda1/home/z/Documents/Projects/egui_graphs/src/metadata.rs:85:49
    |
85  |         ui.data_mut(|data| data.get_persisted::<Metadata>(Id::NULL).unwrap_or_default())
    |                                                 ^^^^^^^^ the trait `serde::ser::Serialize` is not implemented for `metadata::Metadata`
    |
    = help: the following other types implement trait `serde::ser::Serialize`:
              bool
              char
              isize
              i8
              i16
              i32
              i64
              i128
            and 285 others
    = note: required for `metadata::Metadata` to implement `SerializableAny`
note: required by a bound in `IdTypeMap::get_persisted`
   --> /home/z/.cargo/registry/src/index.crates.io-6f17d22bba15001f/egui-0.25.0/src/util/id_type_map.rs:397:29
    |
397 |     pub fn get_persisted<T: SerializableAny>(&mut self, id: Id) -> Option<T> {
    |                             ^^^^^^^^^^^^^^^ required by this bound in `IdTypeMap::get_persisted`

error[E0277]: the trait bound `for<'a> metadata::Metadata: serde::de::Deserialize<'a>` is not satisfied
   --> /mnt/sda1/home/z/Documents/Projects/egui_graphs/src/metadata.rs:85:49
    |
85  |         ui.data_mut(|data| data.get_persisted::<Metadata>(Id::NULL).unwrap_or_default())
    |                                                 ^^^^^^^^ the trait `for<'a> serde::de::Deserialize<'a>` is not implemented for `metadata::Metadata`
    |
    = help: the following other types implement trait `serde::de::Deserialize<'de>`:
              bool
              char
              isize
              i8
              i16
              i32
              i64
              i128
            and 285 others
    = note: required for `metadata::Metadata` to implement `SerializableAny`
note: required by a bound in `IdTypeMap::get_persisted`
   --> /home/z/.cargo/registry/src/index.crates.io-6f17d22bba15001f/egui-0.25.0/src/util/id_type_map.rs:397:29
    |
397 |     pub fn get_persisted<T: SerializableAny>(&mut self, id: Id) -> Option<T> {
    |                             ^^^^^^^^^^^^^^^ required by this bound in `IdTypeMap::get_persisted`

error[E0277]: the trait bound `metadata::Metadata: serde::ser::Serialize` is not satisfied
   --> /mnt/sda1/home/z/Documents/Projects/egui_graphs/src/metadata.rs:90:45
    |
90  |             data.insert_persisted(Id::NULL, self);
    |                  ----------------           ^^^^ the trait `serde::ser::Serialize` is not implemented for `metadata::Metadata`
    |                  |
    |                  required by a bound introduced by this call
    |
    = help: the following other types implement trait `serde::ser::Serialize`:
              bool
              char
              isize
              i8
              i16
              i32
              i64
              i128
            and 285 others
    = note: required for `metadata::Metadata` to implement `SerializableAny`
note: required by a bound in `IdTypeMap::insert_persisted`
   --> /home/z/.cargo/registry/src/index.crates.io-6f17d22bba15001f/egui-0.25.0/src/util/id_type_map.rs:376:32
    |
376 |     pub fn insert_persisted<T: SerializableAny>(&mut self, id: Id, value: T) {
    |                                ^^^^^^^^^^^^^^^ required by this bound in `IdTypeMap::insert_persisted`

error[E0277]: the trait bound `for<'a> metadata::Metadata: serde::de::Deserialize<'a>` is not satisfied
   --> /mnt/sda1/home/z/Documents/Projects/egui_graphs/src/metadata.rs:90:45
    |
90  |             data.insert_persisted(Id::NULL, self);
    |                  ----------------           ^^^^ the trait `for<'a> serde::de::Deserialize<'a>` is not implemented for `metadata::Metadata`
    |                  |
    |                  required by a bound introduced by this call
    |
    = help: the following other types implement trait `serde::de::Deserialize<'de>`:
              bool
              char
              isize
              i8
              i16
              i32
              i64
              i128
            and 285 others
    = note: required for `metadata::Metadata` to implement `SerializableAny`
note: required by a bound in `IdTypeMap::insert_persisted`
   --> /home/z/.cargo/registry/src/index.crates.io-6f17d22bba15001f/egui-0.25.0/src/util/id_type_map.rs:376:32
    |
376 |     pub fn insert_persisted<T: SerializableAny>(&mut self, id: Id, value: T) {
    |                                ^^^^^^^^^^^^^^^ required by this bound in `IdTypeMap::insert_persisted`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `egui_graphs` (lib) due to 4 previous errors
[Finished running. Exit status: 101]
^C2024-01-11T19:53:06.750-06:00 - WARN - Could not pass on signal to command: I/O error: No such process (os error 3)

❯ cargo add --path ../../../egui_graphs --features serde
      Adding egui_graphs (local) to dependencies.
             Features:
             + serde
             - egui_persistence
             - events
❯ cargo watch -x "run"
[Running 'cargo run']
   Compiling petgraph v0.6.4
   Compiling egui_graphs v0.17.1 (/mnt/sda1/home/z/Documents/Projects/egui_graphs)
error[E0277]: the trait bound `metadata::Metadata: Serialize` is not satisfied
   --> /mnt/sda1/home/z/Documents/Projects/egui_graphs/src/metadata.rs:85:49
    |
85  |         ui.data_mut(|data| data.get_persisted::<Metadata>(Id::NULL).unwrap_or_default())
    |                                                 ^^^^^^^^ the trait `Serialize` is not implemented for `metadata::Metadata`
    |
    = help: the following other types implement trait `Serialize`:
              bool
              char
              isize
              i8
              i16
              i32
              i64
              i128
            and 299 others
    = note: required for `metadata::Metadata` to implement `SerializableAny`
note: required by a bound in `IdTypeMap::get_persisted`
   --> /home/z/.cargo/registry/src/index.crates.io-6f17d22bba15001f/egui-0.25.0/src/util/id_type_map.rs:397:29
    |
397 |     pub fn get_persisted<T: SerializableAny>(&mut self, id: Id) -> Option<T> {
    |                             ^^^^^^^^^^^^^^^ required by this bound in `IdTypeMap::get_persisted`

error[E0277]: the trait bound `for<'a> metadata::Metadata: Deserialize<'a>` is not satisfied
   --> /mnt/sda1/home/z/Documents/Projects/egui_graphs/src/metadata.rs:85:49
    |
85  |         ui.data_mut(|data| data.get_persisted::<Metadata>(Id::NULL).unwrap_or_default())
    |                                                 ^^^^^^^^ the trait `for<'a> Deserialize<'a>` is not implemented for `metadata::Metadata`
    |
    = help: the following other types implement trait `Deserialize<'de>`:
              bool
              char
              isize
              i8
              i16
              i32
              i64
              i128
            and 299 others
    = note: required for `metadata::Metadata` to implement `SerializableAny`
note: required by a bound in `IdTypeMap::get_persisted`
   --> /home/z/.cargo/registry/src/index.crates.io-6f17d22bba15001f/egui-0.25.0/src/util/id_type_map.rs:397:29
    |
397 |     pub fn get_persisted<T: SerializableAny>(&mut self, id: Id) -> Option<T> {
    |                             ^^^^^^^^^^^^^^^ required by this bound in `IdTypeMap::get_persisted`

error[E0277]: the trait bound `metadata::Metadata: Serialize` is not satisfied
   --> /mnt/sda1/home/z/Documents/Projects/egui_graphs/src/metadata.rs:90:45
    |
90  |             data.insert_persisted(Id::NULL, self);
    |                  ----------------           ^^^^ the trait `Serialize` is not implemented for `metadata::Metadata`
    |                  |
    |                  required by a bound introduced by this call
    |
    = help: the following other types implement trait `Serialize`:
              bool
              char
              isize
              i8
              i16
              i32
              i64
              i128
            and 299 others
    = note: required for `metadata::Metadata` to implement `SerializableAny`
note: required by a bound in `IdTypeMap::insert_persisted`
   --> /home/z/.cargo/registry/src/index.crates.io-6f17d22bba15001f/egui-0.25.0/src/util/id_type_map.rs:376:32
    |
376 |     pub fn insert_persisted<T: SerializableAny>(&mut self, id: Id, value: T) {
    |                                ^^^^^^^^^^^^^^^ required by this bound in `IdTypeMap::insert_persisted`

error[E0277]: the trait bound `for<'a> metadata::Metadata: Deserialize<'a>` is not satisfied
   --> /mnt/sda1/home/z/Documents/Projects/egui_graphs/src/metadata.rs:90:45
    |
90  |             data.insert_persisted(Id::NULL, self);
    |                  ----------------           ^^^^ the trait `for<'a> Deserialize<'a>` is not implemented for `metadata::Metadata`
    |                  |
    |                  required by a bound introduced by this call
    |
    = help: the following other types implement trait `Deserialize<'de>`:
              bool
              char
              isize
              i8
              i16
              i32
              i64
              i128
            and 299 others
    = note: required for `metadata::Metadata` to implement `SerializableAny`
note: required by a bound in `IdTypeMap::insert_persisted`
   --> /home/z/.cargo/registry/src/index.crates.io-6f17d22bba15001f/egui-0.25.0/src/util/id_type_map.rs:376:32
    |
376 |     pub fn insert_persisted<T: SerializableAny>(&mut self, id: Id, value: T) {
    |                                ^^^^^^^^^^^^^^^ required by this bound in `IdTypeMap::insert_persisted`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `egui_graphs` (lib) due to 4 previous errors
[Finished running. Exit status: 101]
^C2024-01-11T19:53:58.996-06:00 - WARN - Could not pass on signal to command: I/O error: No such process (os error 3)

❯ cargo add --path ../../../egui_graphs --features serde,egui_persistence
      Adding egui_graphs (local) to dependencies.
             Features:
             + egui_persistence
             + serde
             - events
❯ cargo add --path ../../../egui_graphs --features serde,egui_persistence,events
      Adding egui_graphs (local) to dependencies.
             Features:
             + egui_persistence
             + events
             + serde
    Updating crates.io index
^[[A       Fetch [===========>                     ] 118 complete; 1 pending                                                                                                                                                                                                                 ^[[A       Fetch [==============>                  ] 214 complete; 3 pending                                                                                                                                                                                                                 
❯ cargo add --path ../../../egui_graphs --features serde,egui_persistence,events
❯ cargo watch -x "run"
````

Rendering graph inside an egui::Window

Hi,

First of all thanks for this amazing project.

My trying to accomplish a simple task. Rendering a graph inside of an egui::Window.
When following your examples and rendering inside an egui::CentralPanel everything work as expected :

image

But when doing the same approach with an egui::Window it fails, and nothing show up (empty egui::Window).

image

The code of my egui::Window is the following if you want to try reproduce :

(using the "basic" example you provided but with an egui::Window)

egui::Window::new(RichText::new(format!("test_title")).color(graph.sync_color))
    .resizable(true)
    .open(&mut graph.is_visible)
    .show(ctx, |ui| {
        ui.add(&mut GraphView::<
            _,
            _,
            _,
            _,
            DefaultNodeShape,
            DefaultEdgeShape,
        >::new(&mut selector.force_directed_graph.as_mut().unwrap()));
});

Or if you could provide a working example using an egui::Window instead of an egui::CentralPanel. It could help me a lot debugging.

Versions (toml) :

egui_extras = { version = "0.25.0", features = ["svg", "image"]}
eframe = { version = "0.25.0", default-features = false, features = [
    "accesskit",     # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies.
    "default_fonts", # Embed the default egui fonts.
    "glow",          # Use the glow rendering backend. Alternative: "wgpu".
    "persistence",   # Enable restoring app state when restarting the app.
] }
egui_tracing = "0.2.1"
petgraph = "0.6"
egui_graphs = { version = "0.18.0", features = ["egui_persistence"]}

Usability problems

Now users are facing very verbose generic definitions and multiple steps to perform simple task on elements/graph updates.

I will keep this ticket here and definitely think of some optimizations of the process or introducing macros before coming to stable releases.

[egui::Windows compatibility] Graph rendering stay on top of all egui::Windows

Hi,

Thanks a lot for the quick bug fix that makes compatibility with egui::Windows available.

I have discovered a new bug.

Graph rendering stay on top of all the egui::Windows even if the Window where the graph is not focused.
I tested different configuration of GraphView, always with the same behavior.

image

My versions (using last commit version on master) :

egui = "0.26.0"
egui_extras = { version = "0.26.0", features = ["svg", "image"]}
eframe = { version = "0.26.0", default-features = false, features = [
    "accesskit",     # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies.
    "default_fonts", # Embed the default egui fonts.
    "glow",          # Use the glow rendering backend. Alternative: "wgpu".
    "persistence",   # Enable restoring app state when restarting the app.
] }
egui_tracing = "0.2.1"

serde = { version = "1", features = ["derive"] }
serde_json = { version = "1.0", features = ["raw_value"]}
ron = "0.8"
egui_graphs = { git = "https://github.com/blitzarx1/egui_graphs", branch = "master", features = ["egui_persistence", "serde"]}

unwrap panic in graph.rs, `edge_by_screen_pos`

edge_by_screen_pos panics if you are clicking and holding a node while extending the graph.
Seems to be because self.g.edge_endpoints(e.id()) returns None.

The following seems to work as a fix, but not sure if this is a bigger bug.

    pub fn edge_by_screen_pos(&self, meta: &Metadata, screen_pos: Pos2) -> Option<EdgeIndex<Ix>> {
        let pos_in_graph = meta.screen_to_canvas_pos(screen_pos);
        for (idx, e) in self.edges_iter() {
            let (idx_start, idx_end) = match self.g.edge_endpoints(e.id()) {
                Some((start, end)) => (start, end),
                None => continue,
            };
            let start = self.g.node_weight(idx_start).unwrap();
            let end = self.g.node_weight(idx_end).unwrap();
            if e.display().is_inside(start, end, pos_in_graph) {
                return Some(idx);
            }
        }

        None
    }

Examples throw FnMut error

The following error is thrown when trying to run examples;

expected a 'FnMut(&mut Ui)' closure, found 'GraphView<'_, (), ()>

On lines in the update function where we ui.add(&mut GraphView::<...

Non-standard Index size

Graph can only be created w/ the standard DefaultIx size (u32). It would be nice if it was generic over different index sizes so it can support any petgraph stable graph. I can help make this change.

About project roadmap

I really like this repository, and I was wondering what the future maintenance plans are? Are there any plans to implement a demo similar to the blueprints in Unreal, with effects similar to egui_node_graph or imgui-node-editor?

Add ability to remove nodes

Removing nodes is currently broken atm if you add more nodes afterwards.
This is probably because the indexes get out of sync between egui_graphs and petgraph.
You can observe the bug in the configurable example by reducing and then increasing the node count. Nodes end up looping back to themselves (which is the same behavior I see)

Custom `Layout` mechanism

I think it will be relevant to have a possibility for the user providing custom layout for the widget which will compute node position on every frame from node and graph state.

Several points should be noted:

  1. Layout and drag conflicts.
  2. Layout and user set location conflicts.
  3. Layout is a stateful implementation, which keeps state for example in Graph wrapper.
  4. There also should be provided a default implementation of the Layout trait.
  5. Passing layout can be done via constructor methods or pure generics approach as it is done with NodeDisplay and EdgeDisplay.

To showcase this new feature configurable example can be used where forces simulation logic and node positions computation will become implementation of the Layout trait.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.