-
Notifications
You must be signed in to change notification settings - Fork 24
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
Add CSV export to NX Line (and other export improvements) #1763
base: main
Are you sure you want to change the base?
Conversation
@@ -66,6 +66,15 @@ export interface Dims { | |||
|
|||
export type IgnoreValue = (val: number) => boolean; | |||
|
|||
export interface ExportEntry { | |||
format: string; | |||
url: ExportURL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
url
used to be option; now it isn't.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about renaming ExportEntry
to ExportFunction
or Exporter
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExportEntry
was already defined in ExportMenu
before. It's meant to type an entry in the export menu, so I think the naming is good as is.
BuiltInExporter
is the term I used for functions that generate the export payload in the mapped components. The provider then generates an ExportURL
async function that invokes this exporter, and the mapped component puts that ExportURL
in an ExportEntry
to pass to the toolbar.
/approve |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That looks quite good!
There are a few things that bother me a bit:
- The implicit coupling between the
formats
and the record of built-in exporters passed touseExportEntries
- Depending on where you look in the code, it can be difficult to know the priority between
getExportURL
,_getExportURL
and the built-in exporter.
But all and all, the fact that this PR addresses several long-standing and difficult issues at once is a testimony of its relevance.
@@ -66,6 +66,15 @@ export interface Dims { | |||
|
|||
export type IgnoreValue = (val: number) => boolean; | |||
|
|||
export interface ExportEntry { | |||
format: string; | |||
url: ExportURL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about renaming ExportEntry
to ExportFunction
or Exporter
?
: new URL(`data:text/plain,${encodeURIComponent(finalCsv)}`); | ||
}; | ||
} | ||
return async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should you not check that format
is csv
here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, no because the mock provider relies entirely on the viewer providing a builtInExporter
. It doesn't care which format the exporter is for; it just invokes it.
To make that clearer, I've renamed the local variable csv
(used to store the result of calling builtInExporter
) to payload
.
8099bd0
to
1350e7f
Compare
1350e7f
to
536c8ad
Compare
I've updated the documentation of the
Agreed, not perfect, but the types ensure that the exporter record cannot contain formats that are not in the
Totally agree as well, it gets quite confusing. This actually prompted me to update the documentation of the Eventually I'm hoping to at least remove the However, simply adding an |
CSV export for NX Line
The following columns are exported, in order, when the corresponding datasets are present in the
NXdata
group: values, abscissas, errors, aux1, aux1 errors, aux2, aux2 errors, ... For instance, with mock dataset/nexus_entry/spectrum_with_aux
, the CSV file looks like this:Additional improvements
Built-in, provider-agnostic CSV and JSON exports
CSV and JSON exports are now built into the viewer, which means that consumer applications (VS Code, myHDF5, etc.) no longer have to implement them themselves. All that's needed is for providers to include a very basic implementation of the
getExportURL
method internally.Thanks to this, the four provider demos (h5grove, h5wasm, HSDS and mock) now all support exporting to CSV/JSON and do so in a consistent way — i.e. the CSV is always generated locally (it used to be generated remotely in the h5grove demo).
Export compound scalar/array datasets to CSV
Respect transposition when exporting 2D dataset to CSV in Matrix vis
Summary
The tables below summarise export capabilities out of the box before/after this PR for every visualization/format/provider combination. "Out of the box" means without any consumer-provided export implementation.
BEFORE
H5GroveProvider
H5WasmProvider
HsdsProvider
MockProvider
AFTER
H5GroveProvider
H5WasmProvider
HsdsProvider
MockProvider
Implementation
Challenges and early attempts
Providers shouldn't know about NeXus, so I discarded the following solutions quickly:
getExportURL
) along with the main dataset'svalue
;NXdata
metadata and letting the provider get the values again internally.👉 NeXus exports have to be generated in the viewer (well, at least the export generation functions have to be prepared in the viewer...)
Currently, providers all accept a
getExportURL
prop, which enables a lot of different scenarios: it allows consumers to generate the exports themselves, to generate one-time download links, etc. It's also what allows exports to work in the VS Code extension, as the webview has to generate the export payload and send it to the extension host so it can be saved to disk.Being able to implement NPY and TIFF exports in the viewer to get rid of
getExportURL
entirely would have made things a lot easier. After all, we already retrieve the data from the providers, so in a way, they've already done their job. I would then have provided another kind of API to allow consumers of theApp
component to customise the exports and hook onto the exports process — providers would no longer have had anything to do with exports... Of course I discarded this option as well, as generating NPY and TIFF in JS is not trivial, and it's not really something we want to maintain long-term.So there wasn't really any other option than to find a way to mix both viewer-side generation and provider-side generation, while continuing to provide flexibility for consumers.
I tried generating export entries both in the providers and in the viewers and then merging them, but this made it really hard to comprehend which exports would end up available in each visualization.
Proposed solution
In the end, I settled on a solution that involves passing an "exporter" function to
getExportURL
instead of the rawvalue
array.I introduce a hook called
useExportEntries
that I call from the mapped components. Internally, it callsgetExportURL
, passing the exporter for the given format if any:In the providers, I then make sure to return an
ExportURL
function that callsbuiltInExporter
if it's defined: