Basic plotting support
This is a draft for adding basic plotting support to Numbat. Contrary to the first attempt in #147, we now implement the basic data preprocessing step in Numbat itself, using the new struct and List data types. We can now also implement this as a normal function, instead of having to introduce a new command.
We can now write something like:
let A0 = 3 cm
let ω = 20 Hz
let λ = 1.5 s
fn amplitude(t) = A0 exp(-t/λ) cos(ω t)
line_plot(amplitude, 0 s, 8 s) |>
xlabel("Time") |>
ylabel("Amplitude") |>
show
and get the following plot. The xlabel/ylabel calls are optional. Note how the units here are inferred and added to the axis descriptions:
Similarly, we can create a simple bar chart:
let names = ["Iron", "Copper", "Silver", "Gold", "Platinum"]
let elements = map(element, names)
fn density(e: ChemicalElement) -> MassDensity = e.density
let densities = map(density, elements)
bar_chart(densities) |>
xlabels(names) |>
value_label("Density") |>
show
Open points:
- This would be much more powerful if we had #452. This would allow us to rewrite
line_plot(amplitude, 0 s, 8 s)intoline_plot(amplitude) |> xlim(0 s, 8 s), for example. It would also allow us to implementylim(…, …). We could also store the function inside theLinePlotstruct instead of storing all the data points. We would only convert to data points before passing it to the FFI - It would be nice to be able to move the
|>reverse-apply operators in the example above to the next line. To have it look likeline_plot(amplitude, 0 s, 8 s) |> xlabel("Time") |> ylabel("Amplitude") |> show
For CLI usage, I wonder if we could hook it up to ratatui's canvas or chart widgets. This probably wouldn't be all that useful in practice, but might be fun anyway :)
For CLI usage, I wonder if we could hook it up to ratatui's canvas or chart widgets. This probably wouldn't be all that useful in practice, but might be fun anyway :)
Cool. Hadn't heard about ratatui. But yeah, I think a file-based backend and maybe an actual GUI backend might be more useful options.
This changeset now uses plotly, which allows us to open interactive plots in the browser. This is also much lighter in terms of dependencies (but see https://github.com/plotly/plotly.rs/issues/176). And it would also integrate with the web version naturally.
@irevoire I had a new idea for the API. It's a bit hacky, but could be enough for a first draft. I'm now using your enhanced reverse-apply operator to add optional arguments like xlabel and ylabel. Please see the updated PR description for details, and let me know what you think, if you're interested.
Oh yes we can now do a builder pattern quite easily! IMO, that's nicer than having a « Default » implementation for the structure 🤔
On another subject, I’m really starting to think that we may need scopes earlier than I expected:
Plot.line_plot(amplitude, 0 s, 8 s)
|> Plot.xlabel("Time")
|> Plot.ylabel("Amplitude")
|> Plot.show
Something like that seems easier to understand, and it will free up a lot of symbols for the users.
On another subject, I’m really starting to think that we may need scopes earlier than I expected:
Yes, absolutely. It's a problem already since I can't reuse things like ylabel for other plot types. I added a slightly hacky solution for now in order to be able to have just one show function for both line plots and bar plots. The latter of which are now also supported (see PR description).
This is now in a state where it could be merged. A drawback is that the binary size increases by about 4 MiB, which is probably mostly due to https://github.com/plotly/plotly.rs/issues/176