-
Notifications
You must be signed in to change notification settings - Fork 220
Save/Copy Plots #2267
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
Closed
Closed
Save/Copy Plots #2267
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
cb73f23
fmt
AbdulrhmnGhanem 820d3b0
Add export button and recieve its message.
AbdulrhmnGhanem b635d60
Add export plot functionality to `Plots` plots.
AbdulrhmnGhanem 3590b5d
Add export plot functionality to `Plotly` plots.
AbdulrhmnGhanem ae0cc30
Add export plot functionality to `Vega` plots.
AbdulrhmnGhanem a6911a7
Add export plot functionality to `png`/`gif` plots.
AbdulrhmnGhanem 5304922
Remove Plotly builtin export button.
AbdulrhmnGhanem 0226111
Add copy to clipboard functionality.
AbdulrhmnGhanem 8eb8418
eslint
AbdulrhmnGhanem 99e30d4
Run eslint on js files.
AbdulrhmnGhanem 6887b74
Change save button text to `Save Plot`.
AbdulrhmnGhanem 90beaf3
Update CHANGELOG.md
AbdulrhmnGhanem 753d2e2
fixup! Add copy to clipboard functionality.
AbdulrhmnGhanem 0423861
Rename `language-julia.export-plot` -> `language-julia.save-plot`.
AbdulrhmnGhanem 1a0fcdd
clean up
AbdulrhmnGhanem e85da29
Use browser clipboard intead of the native one.
AbdulrhmnGhanem ebe67e9
Remove clipboard script.
AbdulrhmnGhanem 6c4ecc2
Merge branch 'master' into export-plot
AbdulrhmnGhanem 966a976
Remove temp-dir.
AbdulrhmnGhanem 824acb4
Use `SaveDialog` to choose plot path.
AbdulrhmnGhanem f710ca1
Show warning if saving plot failed.
AbdulrhmnGhanem cc5586b
Show warning if copying plot failed.
AbdulrhmnGhanem de9c95b
Merge branch 'master' into export-plot
AbdulrhmnGhanem e8aeb05
Revert code formatting.
AbdulrhmnGhanem c733d67
Merge remote-tracking branch 'origin/master' into export-plot
AbdulrhmnGhanem File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,42 +1,160 @@ | ||
| 'use strict' | ||
|
|
||
| const vscode = acquireVsCodeApi() | ||
|
|
||
| function postMessageToHost(type, val) { | ||
| function postMessageToHost(type, value) { | ||
| if (type) { | ||
| vscode.postMessage({ | ||
| type: type, | ||
| value: val | ||
| type, | ||
| value, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| function getPlotElement() { | ||
| let plot_element = document.getElementById('plot-element') | ||
| const plot_element = document.getElementById('plot-element') | ||
| if (!plot_element) { | ||
| return document.getElementsByTagName('body')[0] | ||
| } | ||
|
|
||
| let canvas = plot_element.getElementsByTagName('canvas')[0] | ||
| if (canvas) { | ||
| return canvas | ||
| } else { | ||
| return plot_element | ||
| } | ||
| const canvas = plot_element.getElementsByTagName('canvas')[0] | ||
| return canvas ?? plot_element | ||
| } | ||
|
|
||
| let interval | ||
| function getImage() { | ||
| const plot = getPlotElement() | ||
| let width = plot.offsetWidth | ||
| let height = plot.offsetHeight | ||
|
|
||
| html2canvas(plot, { height, width }).then((canvas) => { | ||
| postMessageToHost('thumbnail', canvas.toDataURL('png')) | ||
| clearInterval(interval) | ||
| }, (reason) => { | ||
| console.error('Error in taking thumbnail: ', reason) | ||
| }) | ||
| const width = plot.offsetWidth | ||
| const height = plot.offsetHeight | ||
|
|
||
| html2canvas(plot, { height, width }).then( | ||
| (canvas) => { | ||
| postMessageToHost('thumbnail', canvas.toDataURL('png')) | ||
| clearInterval(interval) | ||
| }, | ||
| (reason) => { | ||
| console.error('Error in taking thumbnail: ', reason) | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| function isPlotly() { | ||
| return document.querySelector('#plot-element .plotly') !== null | ||
| } | ||
|
|
||
| function isVega() { | ||
| return document.querySelector('#plot-element.vega-embed') !== null | ||
| } | ||
|
|
||
| const SAVE_PLOT_MESSAGE_TYPE = 'savePlot' | ||
| const REQUEST_SAVE_PLOT_TYPE = 'requestSavePlot' | ||
| const REQUEST_COPY_PLOT_TYPE = 'requestCopyPlot' | ||
| const COPY_FAILED_MESSAGE_TYPE = 'copyFailed' | ||
|
|
||
| /** | ||
| * Fires when a plot request(save/copy) is received, sends a message to the host with | ||
| * i. The plot data url, | ||
| * ii. The index of the plot. | ||
| * @param {number} index | ||
| */ | ||
| function handlePlotSaveRequest(index) { | ||
| const plot = getPlotElement() | ||
| if (isPlotly()) { | ||
| Plotly.Snapshot.toImage(plot, { format: 'svg' }).once('success', (url) => { | ||
| const svg = decodeURIComponent(url).replace(/data:image\/svg\+xml,/, '') | ||
|
|
||
| postMessageToHost(SAVE_PLOT_MESSAGE_TYPE, { svg, index }) | ||
| }) | ||
| } else if (isVega()) { | ||
| const svg = document.querySelector('#plot-element svg').outerHTML | ||
|
|
||
| postMessageToHost(SAVE_PLOT_MESSAGE_TYPE, { svg, index }) | ||
| } else { | ||
| const { src } = plot | ||
|
|
||
| const svg = src.includes('image/svg') | ||
| ? decodeURIComponent(src).replace(/data:image\/svg\+xml,/, '') | ||
| : null | ||
| const png = src.includes('image/png') | ||
| ? src.replace(/data:image\/png;base64,/, '') | ||
| : null | ||
| const gif = src.includes('image/gif') | ||
| ? src.replace(/data:image\/gif;base64,/, '') | ||
| : null | ||
|
|
||
| postMessageToHost(SAVE_PLOT_MESSAGE_TYPE, { svg, png, gif, index }) | ||
| } | ||
| } | ||
|
|
||
| function handlePlotCopyRequest() { | ||
| const plot = document.querySelector('svg') || getPlotElement() | ||
| const isSvg = document.querySelector('svg') !== null | ||
|
|
||
| const width = plot.offsetWidth | ||
| const height = plot.offsetHeight | ||
|
|
||
| if (isSvg) { | ||
| const canvas = document.createElement('canvas') | ||
| const ctx = canvas.getContext('2d') | ||
|
|
||
| const image = new Image() | ||
| const data = new XMLSerializer().serializeToString(plot) | ||
| const blob = new Blob([data], { type: 'image/svg+xml;charset=utf-8' }) | ||
| const url = window.URL.createObjectURL(blob) | ||
|
|
||
| image.onload = () => { | ||
| canvas.width = image.naturalWidth | ||
| canvas.height = image.naturalHeight | ||
| ctx.drawImage(image, 0, 0) | ||
| window.URL.revokeObjectURL(url) | ||
|
|
||
| canvas.toBlob((blob) => { | ||
| navigator.clipboard.write([ | ||
| new ClipboardItem({ | ||
| [blob.type]: blob, | ||
| }), | ||
| ]) | ||
| }) | ||
| } | ||
| image.src = url | ||
| } else { | ||
| html2canvas(plot, { height, width }).then( | ||
| (canvas) => { | ||
| canvas.toBlob((blob) => { | ||
| navigator.clipboard.write([ | ||
| new ClipboardItem({ | ||
| [blob.type]: blob, | ||
| }), | ||
| ]) | ||
| }) | ||
| }, | ||
| (reason) => { | ||
| postMessageToHost(COPY_FAILED_MESSAGE_TYPE) | ||
| console.error(new Error(reason)) | ||
| } | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| window.addEventListener('load', getImage) | ||
| window.addEventListener('load', () => { | ||
| // Remove Plotly builtin export button; it's nonfunctional in VSCode and can confuse users. | ||
| document.querySelector( | ||
| '[data-title="Download plot as a png"]' | ||
| ).style.display = 'none' | ||
| }) | ||
|
|
||
| window.addEventListener('message', ({ data }) => { | ||
| switch (data.type) { | ||
| case REQUEST_SAVE_PLOT_TYPE: | ||
| handlePlotSaveRequest(data.body.index) | ||
| break | ||
| case REQUEST_COPY_PLOT_TYPE: | ||
| handlePlotCopyRequest() | ||
| break | ||
| default: | ||
| console.error(new Error('Unknown plot request!')) | ||
| } | ||
| }) | ||
|
|
||
| interval = setInterval(getImage, 1000) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.