Skip to content

Conversation

@jmcphers
Copy link
Contributor

Adds an implementation of a method to get a plot's metadata, including a derived unique name and the code that was executed to create the plot.

Copy link
Contributor

@DavisVaughan DavisVaughan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One medium sized note about possibly adding a graphics_device::on_execute_request() hook to maybe simplify things, but otherwise looks good.

This all does feel a bit hacky 😅, but is hopefully pretty low stakes and "best effort"

For example, this is a little weird, but i doubt people would care much

# same plot, different names

# "ggplot2 scatter plot {N}"
ggplot(mtcars, aes(x = mpg, y = cyl)) + geom_point() + geom_line()

# "ggplot2 line chart {N}"
ggplot(mtcars, aes(x = mpg, y = cyl)) + geom_line() + geom_point()

I also asked Thomas if he thought ggplot2 would want to be in charge of registering an S3 method for some kind of ark S3 generic that could provide a "plot kind name", but he thought it would just be best if we kept that all here. He liked the ethos of this feature, but thought it would be inappropriate for ggplot2 itself to try and simplify a (possibly very complex) plot down to a single plot kind, which feels reasonable.

Comment on lines +470 to +478
let info = stored_metadata.get(id);

let plot_metadata = match info {
Some(info) => PlotMetadata {
name: info.name.clone(),
kind: info.kind.clone(),
execution_id: info.execution_id.clone(),
code: info.code.clone(),
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason you need a local version of PlotMetadataInfo over just storing PlotMetadatas in the hash map directly?


#[tracing::instrument(level = "trace", skip_all, fields(id = %id))]
fn process_new_plot_jupyter_protocol(&self, id: &PlotId) {
fn process_new_plot_jupyter_protocol(&self, id: &PlotId, execution_context: Option<(String, String)>) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please reformat this file? I get diffs when I save due to reformatting.

Comment on lines 1275 to +1283

// Extract execution context before req is consumed
let execution_id = req.originator.header.msg_id.clone();
let code = req.request.code.clone();

// Check for pending graphics updates
// (Important that this occurs while in the "busy" state of this ExecuteRequest
// so that the `parent` message is set correctly in any Jupyter messages)
graphics_device::on_did_execute_request();
graphics_device::on_did_execute_request(execution_id, code);
Copy link
Contributor

@DavisVaughan DavisVaughan Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you feel about a push model where handle_execute_request() pushes the active request's execution_id and code to the graphics device side via a new graphics_device::on_execute_request() hook?

i.e. in handle_execute_request() after this

                // Save `ExecuteCode` request so we can respond to it at next prompt
                self.active_request = Some(ActiveReadConsoleRequest {
                    exec_count,
                    request: exec_req,
                    originator,
                    reply_tx,
                });

you'd also call graphics_device::on_execute_request() to push the active request's information over.

My thought was that if you did this, then we could remove the awkward code around sometimes having the execution_id and code being passed through process_changes() and sometimes needing to go request it from RMain.

If this idea works out, then DEVICE_CONTEXT would always have an up to date execution_id and code from the graphics_device::on_execute_request() hook and I think it would simplify the model a good bit.

(The way this works right now is what took the most time for me to review and understand)

file <- paste0("render-", id, ".", format)
file.path(directory, file)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of a personal request, but would you mind if we isolated this code in graphics-kind.R or something like that?

I like that graphics.R is currently just about the main flow of handling the graphics devices

I have a feeling this bit of code might require a few rounds of iteration / might be a common place we have to update in the future, so it would be nice to have it tucked away in its own file (for some reason that makes it easier for me to reason about).

Comment on lines +536 to +538
#' 1. Check .Last.value for high-level plot objects (ggplot2, lattice)
#' 2. Check recording's display list for base graphics patterns
#' 3. Fall back to generic "plot"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the idea is that for ggplot2, the display list isn't really "rich" enough to give you all the details you need to come up with a good name? And instead you ideally need the plot object?

Comment on lines +685 to +693
# Check for grid graphics (ggplot2, lattice)
if (any(grepl("^L_", call_names))) {
return("grid")
}

# Check for base graphics
if (any(grepl("^C_", call_names))) {
return("base")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like just plot is more useful than these?

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

Successfully merging this pull request may close these issues.

3 participants