jupyterlab / frontends-team-compass

A repository for team interaction, syncing, and handling meeting notes across the JupyterLab ecosystem.
https://jupyterlab-team-compass.readthedocs.io/en/latest/
BSD 3-Clause "New" or "Revised" License
59 stars 30 forks source link

Which grid framework should we use for UI elements in jlab core? #122

Open telamonian opened 3 years ago

telamonian commented 3 years ago

regular-table and @lumino/datagrid

For the last few months I've been building tree-finder, a general purpose tree/grid custom element, which I'm now working on making into a new filebrowser UI for jupyterlab.

tree-finder is built on top of the regular-table virtualized grid framework. Recently someone asked me on Gitter why I didn't use our own home-grown @lumino/datagrid instead. In the interest of open discussion and working towards consensus, I'm going to write down the reasons I gave here:

I've now been working on the file handling/filebrowser improvements on and off for the last 15 months, and I did think long and hard originally about using datagrid. Here's my thoughts:

Aside from some details in how the data/header models are implemented, datagrid and regular-table are pretty similar projects, with fairly similar goals. Both offer a way to render an infinite grid and fill it with lazily fetched data. The big difference is in the final rendered representation each produces: datagrid renders as a single <canvas> element, whereas regular-table produces a plain HTML <table> with the expected <tr>, <th>, etc contents.

the <table> approach has a number of advantages:

  • accessibility

i kind of want to do a mike drop and walk off right there, but there's a bunch of other upside to producing a <table> as well:

  • integrates naturally with the existing theme system
  • integrates naturally with the existing icon system
  • native scrolling with actual native scroll bars
  • (potentially) integrates naturally with HTML5 drag-n-drop

And then one last point I originally forgot:

Does it make sense to remove @lumino/datagrid?

@bollwyvl Brought up the point that there are numerous potential uses for an easy to use, consistently themed tree/grid widget throughout the codebase. So if tree-finder and/or regular-table end up being widely used in core, should we then retire @lumino/datagrid? I do like the idea of having multiple options/way of doing things, but one grid framework will be easier to maintain than two, and it's likely that the <canvas> element that datagrid produces will always have some accessibility issues.

Existing Components using @lumino/datagrid

Existing Tree/Grid-like Components

Future Components

Extension Components

jasongrout commented 3 years ago

A couple of quick questions:

  1. Lumino datagrid supports variable-height rows as well. Does regular-table support that? (and do we need that, of course?)
  2. What browsers/versions does regular-table support?
  3. It wouldn't hurt to see what the csv-viewer looks like using regular-table, which could easily be an outside extension that could be installed to compare the two.
telamonian commented 3 years ago
  1. Nope! I consider this to be a bug, though (see jpmorganchase/regular-table#96), and am by coincidence currently working on fixing it.

    @jasongrout The fundamental issue is that regular-table needs to be able to calculate it's "non-virtual" height in px based on the total count of rows, so that it can then correctly set up it's scroll-height. Are you familiar with how datagrid is able to get around this?

  2. regular-table's build and test-suite are targeted at chrome and ios safari, but from my own experience I'm confident at this point that it also meets the jlab standard of "last 2 major versions of chrome, firefox, and desktop safari".

  3. I can look into making that happen, though I probably won't have the free time for it for a bit. In the meantime, there is a live example of a regular-table based spreadsheet that you can check out, if you're interested.

jasongrout commented 3 years ago
  1. @jasongrout The fundamental issue is that regular-table needs to be able to calculate it's "non-virtual" height in px based on the total count of rows, so that it can then correctly set up it's scroll-height. Are you familiar with how datagrid is able to get around this?

Yes, that is one of the tricky things that datagrid does that sets it apart from other grid implementations. Chris worked a lot on a datastructure for this. I think it is here: https://github.com/jupyterlab/lumino/blob/master/packages/datagrid/src/sectionlist.ts

I looked a bit at the regular-table library, and it feels impressively fast.

telamonian commented 3 years ago

I looked a bit at the regular-table library, and it feels impressively fast.

Yeah, the guy who came up with the core idea/code is both a minimalist and a perfectionist. regular-table doesn't do everything you might hope it would do, but what it does it does fast and with a tiny footprint. And since the result is a plain HTML table, you're more or less free to extend/customize it how you like. For example, here's a fully working version of Minesweeper implemented via a <regular-table>. A bit zany, but it does a good job of showing off one of the nice features, which is that, in addition to string and number, your data model is free to pass in any arbitrary DOM Node as the value for a cell.

I read through https://github.com/jupyterlab/lumino/blob/master/packages/datagrid/src/sectionlist.ts. It does indeed seem like an efficient datastructure for managing a known sequence of heterogenous heights. However, it still doesn't answer the central mystery: if the rows are provided by a lazy data model, how can DataGrid apriori determine all of the row heights before loading all of the row data, much less rendering all of the rows?

jasongrout commented 3 years ago

I read through https://github.com/jupyterlab/lumino/blob/master/packages/datagrid/src/sectionlist.ts. It does indeed seem like an efficient datastructure for managing a known sequence of heterogenous heights. However, it still doesn't answer the central mystery: if the rows are provided by a lazy data model, how can DataGrid apriori determine all of the row heights before loading all of the row data, much less rendering all of the rows?

IIRC, it assumes a row has the default height until known otherwise, I think? It's been a while since I looked at the code very closely.

ellisonbg commented 3 years ago

Also, it implements its own scroll bar and scrolling model which may relieve (don't remember the details) the need to compute the sizes of all hidden cells above/left of the visible ones.

On Mon, Mar 15, 2021 at 7:17 PM Jason Grout @.***> wrote:

I read through https://github.com/jupyterlab/lumino/blob/master/packages/datagrid/src/sectionlist.ts. It does indeed seem like an efficient datastructure for managing a known sequence of heterogenous heights. However, it still doesn't answer the central mystery: if the rows are provided by a lazy data model, how can DataGrid apriori determine all of the row heights before loading all of the row data, much less rendering all of the rows?

IIRC, it assumes a row has the default height until known otherwise, I think? It's been a while since I looked at the code very closely.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/jupyterlab/team-compass/issues/122#issuecomment-799890871, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAGXUDRMPXKDUANQNMEJGLTD25SVANCNFSM4ZHNQVDA .

-- Brian E. Granger

Principal Technical Program Manager, AWS AI Platform @.***) On Leave - Professor of Physics and Data Science, Cal Poly @ellisonbg on GitHub

telamonian commented 3 years ago

Also, it implements its own scroll bar and scrolling model which may relieve (don't remember the details) the need to compute the sizes

I could definitely imagine some kind of heuristic, but it won't be magic. There's going to be a limit as to what it can gracefully handle. For example, say you have a dataset that holds 100 rows of default height followed by 100 rows of triple height. On first render, the DataGrid will show ~60 rows of default height, and the scroll thumb will be at the top of the scroll bar. What's supposed to happen if you then click on the middle of the scroll bar? It seems clear that no matter what you do, the scroll. behavior in this case will be very different from that of an eagerly rendered table.

I suppose one reasonable thing would be to simply scroll to a middle index (eg 100), but again, that may feel surprising from a UX perspective, since the first half of the scroll bar will end up representing 1/4 of the table's height, while the second half of the scroll bar will represent 3/4.

I'll take a dive into the DataGrid and figure out some of these details

fcollonval commented 3 years ago
1. The fundamental issue is that `regular-table` needs to be able to calculate it's "non-virtual" height in px based on the total count of rows, so that it can then correctly set up it's scroll-height. Are you familiar with how `datagrid` is able to get around this?

@telamonian another source of inspiration may be react-window.

telamonian commented 3 years ago

In any case, for most of the applications I have in mind (I'm still primarily focused on the filebrowser), variable row height is more of a nice thing to have than it is a needed feature. But I will keep looking into it

nmichaud commented 3 years ago

@jasongrout The fundamental issue is that regular-table needs to be able to calculate it's "non-virtual" height in px based on the total count of rows, so that it can then correctly set up it's scroll-height. Are you familiar with how datagrid is able to get around this?

Lumino Datagrid requires the row sizes to be known apriori or the grid scrolling calculations will be incorrect. The sectionlist object maintains a default row size and then efficiently stores rows/columns with differing heights/widths. As you can see from this comment - https://github.com/jupyterlab/lumino/blob/master/packages/datagrid/src/sectionlist.ts#L100-L104, resetting all sections to a default size is linear in number of resized sections instead of O(1). You could leverage a similar type of object for regular-table to support variable size rows/columns.

telamonian commented 3 years ago

Lumino Datagrid requires the row sizes to be known apriori

@nmichaud Thanks for your insight. That was my takeaway from reading through the code in @lumino/datagrid. But there's 7000+ lines of code in that package, so it's nice to get some confirmation.

It would be nice to get something like SectionList into regular-table, along with the related support for explicit row heights. I opened an issue for it at jpmorganchase/regular-table#122

aiqc commented 3 years ago

Should the grid viewer get its own icon in the left sidebar and launcher? Rather than just appearing when the user opens a csv file.

image

image

It would be amazing if this supported multi-dimensional structures: rows x column x depth. e.g in-memory 3D numpy arrays and .npy files.

jasongrout commented 3 years ago

This issue is on the 4.0 planning project. In the JupyterLab dev meeting this morning, we did not find anyone who was willing to do this by the end of the year to make the 4.0 release. If there is someone willing to work on this, please comment here (and please come to the JLab dev meeting and let us know). If no one has volunteered by next week (10 Nov), we'll take it off of the 4.0 planning project. If someone volunteers to do it by the end of the year, we can always put it back on the 4.0 roadmap.

JasonWeill commented 2 years ago

MUI Data Grid and MUI Table do not use canvas rendering, are more friendly with CSS customization, and are open source under the MIT License. My team has been using MUI Table in the Jupyter Scheduler project.