Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

recursively expand all child leaves. #31

Open
sandercm opened this issue Apr 27, 2024 · 6 comments
Open

recursively expand all child leaves. #31

sandercm opened this issue Apr 27, 2024 · 6 comments

Comments

@sandercm
Copy link
Contributor

I would like to add an event handler that opens all child leaves when a parent leave is selected. Currently it seems to only be possible to open 1 level? I have a local fork where I make identifier public on Item this lets me create filters but getting the currently selected item and using it's identifier to filter my tree state.

Would there be interest in my creating a PR for this, or possibly making identifier a field that's accessible from outside the crate not sure if this currently is possible?

@EdJoPaTo
Copy link
Owner

im not entirely sure I entirely understand what you are trying to accomplish so my answer might not perfectly fit. But this is what came to mind when reading it:

You can open a lot of items but you need open the parent items too.

On mqttui I did something like that:

https://github.com/EdJoPaTo/mqttui/blob/27dca3f099d6cb057870eefd31217703f232e207/src/interactive/mod.rs#L674-L676

maybe it’s a good idea to include something like that into here?

@sandercm
Copy link
Contributor Author

That could work, I'm basically trying to visualize json. When I press a button I want to expand the parent aswell as all it's children. Basically call open() I think with the parent and all it's children. I had trouble filtering my vec of items because I couldn't get the identifier out of a TreeItem. But this might just be a misunderstanding of the library. (I'm also still learning Rust so maybe I just misunderstood how it worked.)

In a related note my tree could be very large and I noticed that when I have to copy it in Tree::new() during every render this would be very slow. So I was wondering if you are suppose to create a new Tree object during every render or if you have to persist it somewhere and just call .render() every iteration?

I have a local fork where I changed Tree to take a reference to a vec of items instead of moving it in there.

@EdJoPaTo
Copy link
Owner

EdJoPaTo commented May 1, 2024

In a related note my tree could be very large and I noticed that when I have to copy it in Tree::new() during every render this would be very slow.

As this is one of the issues I created some benchmarks and performance improvements on rendering. Interestingly the item creating is the one which takes longer, so there are also improvements needed.

The basic idea of a TreeItem is to only be a thin wrapper around the original data referencing it when possible. So the original data should be persisted and the TreeItem should only exist a short amount of time. The benchmarks suggest that the item creating takes longer than I hoped, so this should be improved.

Also as a side note: I moved the JSON handling of mqttui into this library: 685130c
This still needs some performance improvements… Hopefully it will be useful with this common use case.

When I press a button I want to expand the parent aswell as all it's children. Basically call open() I think with the parent and all it's children. I had trouble filtering my vec of items because I couldn't get the identifier out of a TreeItem.

Hm… yes…
With my refactoring of flatten (2784c45) this also got a bit more annoying to do directly?

It would be possible to create a open_all_visible_below() with that new internal last_visible_identifiers but that requires a render to be updated so not ideal…

What I currently think is about the item creation. When the items are created the identifier is also known. This is how I did the search feature for mqttui: check the base data structure what should be opened, create the identifiers from that and use state.open() for all of them…
Not ideal either…

Guess all the options currently are… not really helpful.

@sandercm
Copy link
Contributor Author

sandercm commented May 2, 2024

In a related note my tree could be very large and I noticed that when I have to copy it in Tree::new() during every render this would be very slow.

As this is one of the issues I created some benchmarks and performance improvements on rendering. Interestingly the item creating is the one which takes longer, so there are also improvements needed.

The basic idea of a TreeItem is to only be a thin wrapper around the original data referencing it when possible. So the original data should be persisted and the TreeItem should only exist a short amount of time. The benchmarks suggest that the item creating takes longer than I hoped, so this should be improved.

Also as a side note: I moved the JSON handling of mqttui into this library: 685130c This still needs some performance improvements… Hopefully it will be useful with this common use case.

When I press a button I want to expand the parent aswell as all it's children. Basically call open() I think with the parent and all it's children. I had trouble filtering my vec of items because I couldn't get the identifier out of a TreeItem.

Hm… yes… With my refactoring of flatten (2784c45) this also got a bit more annoying to do directly?

It would be possible to create a open_all_visible_below() with that new internal last_visible_identifiers but that requires a render to be updated so not ideal…

What I currently think is about the item creation. When the items are created the identifier is also known. This is how I did the search feature for mqttui: check the base data structure what should be opened, create the identifiers from that and use state.open() for all of them… Not ideal either…

Guess all the options currently are… not really helpful.

I was using a a structure like this

pub struct JsonTree {
    state: TreeState<u64>,
    items: Vec<TreeItem<'static, u64>>,
}

But instead of using TreeItem as a wrapper around my data I had a function that parsed serde_json into Vec<TreeItem<'static, u64>> which I also used to store the data this way I didn't have to keep the json objectin memory but maybe I should rework this so TreeItem holds a reference to the original value.

One thing I want to note though when you visualise a json tree (my function is almost identical to what you do here, but one difference is that I apply styles to the object while I'm parsing them. This is a feature that's pretty important to me as a I like color coding to help visualize my data while debugging.

But the main issue I had in my first approach which you also have is when you try to visualize array's and objects this information get's lost atm. I have a pretty hacky method where I just add a leaf containing a single char to show it's an array/object. Example from my current impl.

serde_json::Value::Array(arr) => {
    items.push(tui_tree_widget::TreeItem::new_leaf(
        rand::random::<u64>(),
        "[",
    ).style(ratatui::style::Color::Red.into()));
    for value in arr {
        items.extend(convert_serde_to_treeview(value));
    }
    items.push(tui_tree_widget::TreeItem::new_leaf(
        rand::random::<u64>(),
        "]",
    ).style(ratatui::style::Color::Red.into()));
}

Maybe you know of a better way to visualize this object/array information?

@EdJoPaTo
Copy link
Owner

EdJoPaTo commented May 4, 2024

Just as a side note, I added colorisation to the json stuff b038779 (still unreleased and probably not the best for performance yet)

@EdJoPaTo
Copy link
Owner

To give an update on this issue:

With the upcoming TreeData trait (#34) not all identifiers are known to the Tree anymore, only the ones currently open. (which speeds up the performance a lot)

The JSON data in your case is known to you, so it will be possible to get the identifiers from the data directly.

For the benchmarks I created a somewhat horrible function for that. It's definitely not perfect but suits the need of the benchmark in the current state. Will probably be optimized in the future.

fn open_all(state: &mut TreeState<Selector>, json: &serde_json::Value, selector: &[Selector]) {
match json {
serde_json::Value::Null
| serde_json::Value::Bool(_)
| serde_json::Value::Number(_)
| serde_json::Value::String(_) => {}
serde_json::Value::Array(array) if array.is_empty() => {}
serde_json::Value::Array(array) => {
state.open(selector.to_vec());
for (index, value) in array.iter().enumerate() {
let mut child_selector = selector.to_vec();
child_selector.push(Selector::ArrayIndex(index));
open_all(state, value, &child_selector);
}
}
serde_json::Value::Object(object) if object.is_empty() => {}
serde_json::Value::Object(object) => {
state.open(selector.to_vec());
for (key, value) in object {
let mut child_selector = selector.to_vec();
child_selector.push(Selector::ObjectKey(key.clone()));
open_all(state, value, &child_selector);
}
}
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants