Skip to content

Add export menu with SVG/PNG support and export history panel#1085

Open
Sahilkatkamwar wants to merge 1 commit intofossasia:masterfrom
Sahilkatkamwar:add-svg-export
Open

Add export menu with SVG/PNG support and export history panel#1085
Sahilkatkamwar wants to merge 1 commit intofossasia:masterfrom
Sahilkatkamwar:add-svg-export

Conversation

@Sahilkatkamwar
Copy link

@Sahilkatkamwar Sahilkatkamwar commented Mar 23, 2026

Summary

This PR improves the plot export workflow in Visdom by introducing a unified export menu and export history tracking.

Changes

  • Added export dropdown (SVG / PNG)
  • Removed reliance on Plotly default export button
  • Implemented export history using localStorage
  • Added UI panel to display export history
  • Improved layout with dynamic resizing

Motivation

The current export functionality is limited and does not provide visibility into past exports. This improves usability and workflow for users working with multiple plots.

Testing

  • Verified SVG and PNG export works correctly
  • Confirmed export history updates after each download
  • Tested UI responsiveness when toggling history panel

Screenshots

Screenshot 2026-03-24 010854 image

Summary by Sourcery

Introduce a unified export workflow for plots with selectable formats and an export history panel, while improving layout responsiveness when toggling history.

New Features:

  • Add an export dropdown menu in the pane toolbar to choose SVG or PNG downloads for plots.
  • Persist export metadata in localStorage and expose it via an in-pane export history widget.

Enhancements:

  • Disable Plotly's default image export button in favor of the custom export menu.
  • Adjust plot layout resizing logic to respond to rendering changes and export history panel visibility.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Mar 23, 2026

Reviewer's Guide

Implements a custom export workflow for PlotPane by replacing Plotly’s default image button with a dropdown-based SVG/PNG export menu, persisting export events to localStorage, and adding an in-pane export history view with responsive layout adjustments.

Sequence diagram for custom SVG/PNG export and history logging

sequenceDiagram
  actor User
  participant Pane
  participant PlotPane
  participant Plotly
  participant LocalStorage

  User->>Pane: click saveButton
  Pane->>Pane: toggle exportMenuOpen
  User->>Pane: select Export SVG or Export PNG
  Pane->>PlotPane: handleDownload(format)
  PlotPane->>Plotly: downloadImage(plotlyRef, format, filename)
  PlotPane->>LocalStorage: getItem(visdom_export_log)
  LocalStorage-->>PlotPane: existingLogJson
  PlotPane->>PlotPane: saveExportLog(entry)
  PlotPane->>LocalStorage: setItem(visdom_export_log, updatedLogJson)
  PlotPane->>PlotPane: setExportHistory(loadExportHistory().reverse())
  PlotPane->>Plotly: Plots.resize(plotlyRef)
  PlotPane-->>User: image downloaded

  User->>Pane: select Export history
  Pane->>PlotPane: onShowExportHistory()
  PlotPane->>PlotPane: toggle showExportHistory
  PlotPane->>PlotPane: useEffect(showExportHistory)
  PlotPane->>LocalStorage: getItem(visdom_export_log)
  LocalStorage-->>PlotPane: historyLogJson
  PlotPane->>PlotPane: setExportHistory(loadExportHistory().reverse())
  PlotPane->>Plotly: Plots.resize(plotlyRef)
  PlotPane-->>User: export history panel rendered
Loading

Class diagram for updated Pane and PlotPane components

classDiagram
  class PlotPane {
    +boolean smoothWidgetActive
    +number smoothvalue
    +boolean showExportHistory
    +Array exportHistory
    +loadExportHistory()
    +updateSmoothSlider(value)
    +saveExportLog(entry)
    +handleDownload(format)
  }

  class Pane {
    +string id
    +string title
    +any content
    +Array widgets
    +Array barwidgets
    +boolean enablePropertyList
    +boolean propertyListShown
    +boolean exportMenuOpen
    +handleZoom()
    +handleMouseMove()
    +handleClose()
    +handleDownload(format)
    +handleShowExportHistory()
  }

  PlotPane --> Pane : uses
  PlotPane ..> LocalStorage : persists_export_log
  PlotPane ..> Plotly : triggers_download_and_resize

  class LocalStorage {
    +getItem(key)
    +setItem(key, value)
  }

  class Plotly {
    +downloadImage(target, options)
    +Plots.resize(target)
  }
Loading

File-Level Changes

Change Details Files
Add localStorage-backed export logging and history state to PlotPane and integrate it with the custom download handler.
  • Introduce showExportHistory and exportHistory React state in PlotPane and helper loadExportHistory to read from localStorage safely.
  • Add saveExportLog to append structured export entries to the visdom_export_log key with basic error handling.
  • Extend handleDownload to accept an image format, generate a timestamped filename, call Plotly.downloadImage, persist a log entry (contentID, filename, format, timestamp, title, status), and refresh exportHistory in reverse chronological order.
js/panes/PlotPane.js
Render an export history widget in PlotPane and adjust plot sizing/responsiveness when the history panel is toggled or when plots are rendered.
  • Add a showExportHistory-driven widget that lists export entries with format, timestamp (localized), and status, or a fallback message when empty.
  • Filter widget and barwidget arrays to skip nulls, and adjust the Plotly graph container height based on whether export history is shown.
  • Trigger Plotly.Plots.resize via setTimeout after newPlot() and whenever showExportHistory changes to keep the plot responsive to layout changes.
js/panes/PlotPane.js
Remove usage of Plotly’s built-in image export control and normalize widget defaults.
  • Pass modeBarButtonsToRemove with 'toImage' so the Plotly default download button is hidden.
  • Change smooth_widget_button and smooth_widget initial values from empty strings to null to better reflect React element semantics.
js/panes/PlotPane.js
Add an export dropdown menu to Pane’s toolbar that routes export actions and history toggling to the parent pane.
  • Introduce exportMenuOpen state in Pane to manage visibility of a custom export menu triggered by the existing save button, preventing event propagation so the pane is not unintentionally affected.
  • Render a positioned export-menu popover with buttons for 'Export SVG', 'Export PNG', and 'Export history', wiring them to handleDownload with the appropriate format or to handleShowExportHistory, and closing the menu after selection.
  • Define handleShowExportHistory from props.onShowExportHistory with a no-op default, and default barwidgets to an empty array to simplify consumption.
js/panes/Pane.js

Possibly linked issues

  • #(not provided): PR replaces Plotly default button with a custom SVG/PNG export menu, fulfilling and extending the SVG export request

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • Consider removing the console.log in handleDownload (or guarding it behind a debug flag) to avoid noisy logging in normal usage.
  • The Plotly.Plots.resize calls wrapped in setTimeout are duplicated (after newPlot and in the showExportHistory effect); factoring this into a small helper would reduce repetition and keep resize behavior consistent.
  • saveExportLog reimplements the localStorage parse logic instead of using loadExportHistory; using the helper there would centralize error handling and keep the export log behavior consistent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider removing the console.log in handleDownload (or guarding it behind a debug flag) to avoid noisy logging in normal usage.
- The Plotly.Plots.resize calls wrapped in setTimeout are duplicated (after newPlot and in the showExportHistory effect); factoring this into a small helper would reduce repetition and keep resize behavior consistent.
- saveExportLog reimplements the localStorage parse logic instead of using loadExportHistory; using the helper there would centralize error handling and keep the export log behavior consistent.

## Individual Comments

### Comment 1
<location path="js/panes/PlotPane.js" line_range="57-66" />
<code_context>
+    }
+  };
+
+  const handleDownload = (format = 'svg') => {
+    console.log('handleDownload clicked', contentID, format);
+    const filename = `${contentID}_${new Date()
+      .toISOString()
+      .replace(/[:.]/g, '-')}`;
+
     Plotly.downloadImage(plotlyRef.current, {
-      format: 'svg',
-      filename: contentID,
+      format,
+      filename,
     });
+
+    saveExportLog({
+      contentID,
+      filename,
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Handle `Plotly.downloadImage` success/failure instead of always logging a successful export

`saveExportLog` is always called with `status: 'success'`, but `Plotly.downloadImage` returns a promise and can fail. Chain `.then()` / `.catch()` on `Plotly.downloadImage`, log `status: 'success'` only on resolve, and `status: 'error'` (optionally with the error) on reject so export history reflects actual outcomes.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +57 to 66
const handleDownload = (format = 'svg') => {
console.log('handleDownload clicked', contentID, format);
const filename = `${contentID}_${new Date()
.toISOString()
.replace(/[:.]/g, '-')}`;

Plotly.downloadImage(plotlyRef.current, {
format: 'svg',
filename: contentID,
format,
filename,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): Handle Plotly.downloadImage success/failure instead of always logging a successful export

saveExportLog is always called with status: 'success', but Plotly.downloadImage returns a promise and can fail. Chain .then() / .catch() on Plotly.downloadImage, log status: 'success' only on resolve, and status: 'error' (optionally with the error) on reject so export history reflects actual outcomes.

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.

2 participants